A fully automated CI/CD pipeline for deploying a containerized Remix Weather Application to Azure Kubernetes Service (AKS), with Terraform managing all infrastructure as code and GitHub Actions driving every stage of the pipeline.
- Terraform IaC — Provisions Azure infrastructure (AKS cluster, ACR, networking)
- Static Analysis —
fmt,validate, andtfsecrun on every push - Linting & Planning —
tflintandterraform planrun on pull requests tomain - Apply on Merge —
terraform applyruns automatically when PRs merge tomain - Docker Build & Push — App image built and pushed to ACR, tagged with commit SHA
- AKS Deployment — Deploys to test on PR, production on merge to
main
Trigger: Push to any branch
terraform fmt— enforces formatting standardsterraform validate— checks configuration validitytfsec— scans for security misconfigurations
Trigger: Pull request to main
tflint— lints Terraform files for best practices and provider-specific rulesterraform plan— previews infrastructure changes before merge
Trigger: Push to main (merged pull request)
- Runs
terraform apply -auto-approveto provision or update Azure infrastructure
Trigger: Pull request to main, only when application code changes (app/**)
- Builds the Docker image and pushes it to Azure Container Registry (ACR)
- Tags the image with the commit SHA (e.g.,
weatherapp:abc1234)
Trigger: Application code changes (app/**) only
- Test environment → on pull request to
main - Production environment → on push to
main
| Secret | Description |
|---|---|
AZURE_CREDENTIALS |
Service principal JSON for Azure authentication |
ACR_LOGIN_SERVER |
ACR hostname (e.g., myregistry.azurecr.io) |
ACR_USERNAME |
ACR admin username |
ACR_PASSWORD |
ACR admin password |
AKS_CLUSTER_NAME |
Name of the AKS cluster |
AKS_RESOURCE_GROUP |
Resource group containing the AKS cluster |
ARM_CLIENT_ID |
Azure service principal client ID (for Terraform) |
ARM_CLIENT_SECRET |
Azure service principal secret (for Terraform) |
ARM_SUBSCRIPTION_ID |
Azure subscription ID |
ARM_TENANT_ID |
Azure tenant ID |
Remote state is stored in an Azure Storage Account. Update the backend config in terraform/ to point to your storage account and container before running any workflows.
- Open a PR to
main— triggers static analysis, tflint, terraform plan, Docker build, and test deployment - Merge the PR — triggers terraform apply and production deployment
- Push to any branch — triggers static analysis only
Workflows are path-filtered so infrastructure-only changes do not trigger app deployments and vice versa.
Actions workflows
AKS LoadBalancer External IP
Test Environment
cd terraform/
terraform destroy -auto-approve.
├── .github/
│ └── workflows/
│ ├── terraform-static-analysis.yml
│ ├── terraform-plan.yml
│ ├── terraform-apply.yml
│ ├── docker-build-push.yml
│ └── deploy.yml
├── app/ # Remix Weather Application source
├── terraform/ # Terraform infrastructure code
├── k8s/ # Kubernetes manifests (test + prod)
└── README.md
Terraform GitHub Actions Docker Azure Kubernetes Service Azure Container Registry tfsec tflint Remix Node.js