Infrastructure-as-Code for a multi-environment Blue/Green deployment pipeline on AWS. A single terraform apply provisions the network, registry, CI/CD, and three isolated ECS environments (dev, test, prod) for a containerized Node.js reference app.
- ECS on EC2 β containerized application hosting with an Auto Scaling Group of container-instances (one cluster per environment)
- Application Load Balancer β per-environment ALB with two target groups (blue/green) and two listeners (
:80prod traffic,:8080test traffic) - Auto Scaling Group + ECS Capacity Provider β managed-scaling EC2 capacity for each cluster
- CodePipeline (V2) β two pipelines (
dev,main) triggered by GitHub push via a CodeStar Connection - CodeBuild β Docker image build, Jest test execution, 80 % coverage gate, ECR push
- CodeDeploy β ECS blue/green with a custom
TimeBasedCanaryconfig (30 % traffic for 1 min, then full cutover) and automatic rollback on failure - ECR β KMS-encrypted image registry with scan-on-push
- VPC β 2-AZ network, 2 public subnets, 2 private subnets, 2 NAT gateways
- IAM β five scoped roles (instance / task execution / task / codebuild / codedeploy / codepipeline)
- KMS β single symmetric CMK (key rotation on) shared by S3, ECR, CodeBuild, CodePipeline, CodeDeploy
- S3 β artifact bucket for CodeBuild/CodePipeline
- β Zero-downtime deployments via CodeDeploy ECS blue/green (canary rollout + auto-rollback)
- β Build-once, promote main pipeline (same artifact goes through test β manual-approval β prod)
- β Per-environment isolation β dev, test, and prod each get their own ECS cluster, ALB, and ASG
- β KMS-encrypted artifacts end-to-end (S3, ECR, CodeBuild, CodePipeline, CodeDeploy)
- β
Least-privilege IAM β pipeline role scoped to the services it actually uses (no
ec2:*/s3:*wildcards); execution role and task role are separate identities - β
Consistent tagging via
provider.default_tagsβ no per-resourcetags =plumbing - β IMDSv2 enforced on EC2 container-instances
- β X-Ray distributed tracing (daemon sidecar + SDK middleware)
- β
CI-side security scanning β GitHub Actions workflow runs ASH (Automated Security Helper) + Checkov +
terraform fmt/validateon every PR - β Dependency hygiene β Renovate monthly grouped updates, SonarCloud connected mode
- Terraform >= 1.1.0
- AWS CLI configured with credentials for the target account
- Docker for local testing and image builds
- Node.js >= 18 (the Dockerfile uses Node 24; the reference app's React deps target Node 18+)
- Yarn package manager
- A CodeStar Connection to GitHub, authorized in the AWS console β its ARN is supplied via
github_connection_arninterraform.tfvars
-
Clone the repository
git clone <repository-url> cd Blue_Green_AWS_Terraform
-
Configure AWS credentials
aws configure
-
Customize variables Edit
terraform/terraform.tfvarsβ at minimum setgithub_connection_arn,github_owner,github_repo,main_branch_name,dev_branch_name. -
Initialize Terraform
cd terraform terraform init
State backend: this project currently uses local state (
terraform/terraform.tfstate). Do not use this layout for production. Move state to an S3 backend with a DynamoDB lock table, and never commit state files (they can contain secrets).
cd terraform
terraform plan
terraform apply
terraform output # prints the three ALB DNS names (dev / test / prod)Pushes to the configured branches trigger the pipelines automatically:
- dev branch β
devpipeline β deploys todevenvironment - main branch β
mainpipeline β builds once β deploys totestβ manual approval β deploys toprod
git push origin <dev_branch_name> # or main_branch_nameThe manual approval step happens in the CodePipeline console.
cd Test_App
yarn all-install # installs backend + frontend, builds React client
yarn start # starts Express on port 3000
yarn test # Jest + SupertestThe app is available at http://localhost:3000.
βββ terraform/ # Infrastructure as Code
β βββ main.tf # Root module wiring
β βββ variables.tf # Input variables (validated)
β βββ outputs.tf # ALB DNS outputs per environment
β βββ locals.tf # common_tags merged into provider default_tags
β βββ data.tf # aws_caller_identity
β βββ versions.tf # Terraform + AWS provider pins, default_tags
β βββ terraform.tfvars # Your environment-specific values
β βββ vpc/ # VPC, subnets, IGW, NAT gateways, route tables
β βββ security-groups/ # ALB & ECS security groups
β βββ alb/ # Per-env ALB + blue/green target groups + listeners
β βββ asg/ # Launch template + ASG + capacity provider
β βββ ecs/ # Per-env cluster + service + CW alarm (wraps alb/asg/security-groups)
β βββ task_definition/ # ECS task definitions (app + X-Ray sidecar)
β βββ codebuild/ # CodeBuild project (one per build env)
β βββ codedeploy/ # CodeDeploy app + deployment group (one per env)
β βββ codepipeline_dev/ # Dev pipeline (Source β Build β Deploy-Dev)
β βββ codepipeline_main/ # Main pipeline (Source β Build β Deploy-Test β Approval β Deploy-Prod)
β βββ ecr/ # Private ECR repo (KMS-encrypted, scan-on-push)
β βββ iam/ # All IAM roles & inline policies
β βββ kms/ # Pipeline KMS CMK + alias
β βββ s3/ # Artifact bucket
β βββ codecommit/ # Deprecated (GitHub replaced CodeCommit in v1.0.1)
βββ Test_App/ # Express + React reference application
β βββ buildspec.yml # CodeBuild spec (alternate β the repo-root one is the active one)
β βββ Dockerfile # Multi-stage (client builder β server)
β βββ index.js # Express backend with X-Ray SDK
β βββ package.json
β βββ client/ # React (Create React App) frontend
β βββ tests/ # Jest + Supertest
βββ .github/
β βββ workflows/
β β βββ security-and-terraform.yml # ASH + Checkov + terraform fmt/validate
β βββ CODEOWNERS
β βββ pull_request_template.md
βββ buildspec.yml # Active CodeBuild spec (referenced by CodeBuild projects)
βββ renovate.json # Monthly grouped dep updates
βββ CHANGELOG.md
βββ SECURITY.md
βββ README.md
| Variable | Description | Current value |
|---|---|---|
aws_region |
AWS region | us-east-1 |
project_name |
Resource-name prefix | blue-green-dep-app |
vpc_cidr |
VPC CIDR | 10.0.0.0/16 |
availability_zones |
AZ pair for the subnets | ["us-east-1a", "us-east-1b"] |
asg_ec2_instance_type |
Container-instance type | t2.large |
asg_desired_capacity / min / max |
ASG sizing | 2 / 2 / 4 |
task_definition_cpu / memory |
Task sizing | 256 / 512 |
container_port |
App port | 3000 |
main_branch_name / dev_branch_name |
GitHub branches that trigger the pipelines | master / feature/modifications |
github_connection_arn |
ARN of the CodeStar Connection to GitHub | set in tfvars |
All tags are applied once at the provider level via default_tags:
provider "aws" {
region = var.aws_region
default_tags { tags = local.common_tags }
}Modules do not accept or forward a tags variable β every taggable AWS resource inherits the project tag set automatically. The only exception is the aws_autoscaling_group's tag {} blocks, which AWS requires as a distinct construct.
git push
β
βΌ (CodeStar Connection webhook, V2 pipeline trigger)
CodePipeline
ββ Source β pull repository β S3 artifact (KMS-encrypted)
ββ Build β CodeBuild: docker build, yarn test --coverage (β₯ 80 %), push to ECR
ββ Deploy β CodeDeploy ECS blue/green:
1. register new task set on idle (green) target group
2. route test traffic via ALB :8080 for verification
3. shift 30 % prod traffic (:80) for 1 minute (canary)
4. shift remaining 70 %
5. drain and terminate blue task set
6. auto-rollback on DEPLOYMENT_FAILURE
cd Test_App
yarn test # Jest + Supertest
yarn test --coverage # coverage report (CodeBuild gates on β₯ 80 %)- CloudWatch Logs β auto-created per task (app + X-Ray sidecar streams)
- CloudWatch Metrics β ECS ContainerInsights enabled on every cluster
- CloudWatch Alarm β
CPUReservation β₯ 60 %(note:alarm_actions = []β wire to SNS to make actionable) - ALB health checks β
GET /every 30 s per target group - X-Ray β distributed tracing via the
aws-xray-daemonsidecar + SDK middleware in the app
- Scoped IAM β CodePipeline role is scoped to the services it actually invokes (CodeStar Connections, CodeBuild, CodeDeploy, S3 artifact bucket, ECR describe, KMS, PassRole). The ECS task-execution role is distinct from the task role so image-pull and log-write are separated from application-level permissions.
- IMDSv2 required on the EC2 launch template.
- KMS key rotation enabled on the pipeline CMK.
- ECR image scanning on push.
- ALB β deletion protection, invalid-header drop.
- CI security scanning β every push and PR runs:
terraform fmt -check -recursive+terraform validate- AWS ASH (Automated Security Helper) β Docker image, aggregates Bandit, Checkov, cdk-nag, cfn-nag, detect-secrets, Semgrep, grype, syft, npm-audit
- Checkov standalone β SARIF output uploaded to GitHub Code Scanning
- Terraform state lock β
terraform force-unlock <lock-id> - Pipeline stuck at Source β the CodeStar Connection status must be Available; re-authorize it in the AWS console if it is Pending.
- CodeBuild failing on
dockerdβ the buildspec starts the Docker daemon manually. If it hangs, setprivileged_mode = trueon the CodeBuild project (the documented-supported path). - ECR push rejected with
ImageTagAlreadyExistsExceptionβ only happens if you switch the repository toIMMUTABLEwithout removing the:<env>-latesttag push frombuildspec.yml. - CodeDeploy rollback β CodeDeploy rolls back automatically on
DEPLOYMENT_FAILURE. To force a manual rollback, stop the deployment from the CodeDeploy console.
- Fork and create a feature branch (
git checkout -b feature/my-change) - Commit your changes (
git commit -m 'Describe the change') - Push the branch (
git push origin feature/my-change) - Open a Pull Request β the GitHub Actions workflow will run Terraform checks and security scans automatically.
MIT License β see the LICENSE file.
- Sagar Gupta β @Sagargupta16
- AWS documentation and reference architectures
- Terraform community modules
- Blue/Green deployment patterns popularized by the CodeDeploy-on-ECS integration