All infrastructure deployment and teardown is managed through GitHub Actions workflows located in .github/workflows/ecs/. These workflows automate Terraform operations in a dependency-aware order.
┌─────────────────────────────────────────────────────────────┐
│ Initial Setup (Manual) │
└─────────────────────────────────────────────────────────────┘
│
↓
┌──────────────────────────────────────┐
│ ecs-deploy-hosted-zone.yaml │
│ 1. Deploy S3 state bucket │
│ 2. Deploy Route53 Hosted Zone │
│ 3. [MANUAL] Update DNS nameservers │
│ 4. [MANUAL] Wait for DNS propagation │
└──────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ Full Deployment (On Push to main) │
└─────────────────────────────────────────────────────────────┘
│
↓
┌──────────────────────────────────────┐
│ ecs-deploy-aws-infra.yaml │
│ 1. Test Terraform modules │
│ 2. Deploy S3 state bucket │
│ 3. Deploy ECR │
│ 4. Retrieve SSL certificate │
│ 5. Build and push Docker image │
│ 6. Deploy VPC │
│ 7. Deploy ECS cluster │
│ 8. Deploy ALB │
│ 9. Deploy ECS service │
│ 10. Deploy Route53 routing │
└──────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ Teardown (Manual Trigger) │
└─────────────────────────────────────────────────────────────┘
│
┌──────────────────┴─────────────────────┐
│ │
↓ ↓
┌────────────────────────┐ ┌────────────────────────┐
│ ecs-destroy-aws-infra │ │ ecs-destroy-hosted- │
│ -yaml │ │ zone.yaml │
│ 1. Destroy ECS service │ │ 1. Destroy hosted zone │
│ 2. Destroy routing │ │ 2. Destroy S3 bucket │
│ 3. Destroy ALB │ │ │
│ 4. Destroy SSL cert │ │ │
│ 5. Destroy ECS cluster │ │ │
│ 6. Destroy ECR │ │ │
│ 7. Destroy VPC │ │ │
└────────────────────────┘ └────────────────────────┘
Workflow: ecs-deploy-hosted-zone.yaml
Trigger: Manual (workflow_dispatch)
Purpose: One-time setup of foundational infrastructure
-
deploy-terraform-state-bucket
- Creates S3 bucket for Terraform remote state storage
- Enables versioning for state file history
- Reusable Workflow: Calls
ecs-deploy-terraform-state-bucket.yaml
-
deploy-hosted-zone (depends on: deploy-terraform-state-bucket)
- Creates Route 53 Hosted Zone for the domain
- Initializes Terraform with remote state backend
- Runs
terraform planandterraform applyininfra-ecs/deployment/hosted_zone/ - Required Variables:
common.tfvars(project_name, environment),domain.tfvars(root_domain_name)
- Navigate to AWS Console → Route 53 → Hosted Zones
- Copy the nameserver (NS) records (4 values like
ns-123.awsdns-45.com) - Update DNS at your domain registrar with the Route 53 nameservers
- Wait for DNS propagation (can take 5 minutes to 48 hours, typically < 1 hour)
- Verify propagation: Run
dig NS yourdomain.comor use online DNS checkers
Why This Matters: The SSL certificate validation in the next workflow requires functioning DNS. If DNS hasn't propagated, the certificate validation will fail.
Workflow: ecs-deploy-aws-infra.yaml
Trigger: Push to main branch
Purpose: Complete infrastructure deployment from ECR to running application
-
test-terraform-modules
- Runs Terraform test suite (
run-tests.sh) - Validates all modules before deployment
- Uses mock AWS credentials (tests run in plan mode)
- Working directory:
infra-ecs/
- Runs Terraform test suite (
-
deploy-terraform-state-bucket (depends on: test-terraform-modules)
- Ensures S3 state bucket exists
- Reusable workflow:
ecs-deploy-terraform-state-bucket.yaml
-
deploy-ecr (depends on: test-terraform-modules, deploy-terraform-state-bucket)
- Creates ECR repository if it doesn't exist
- Working directory:
infra-ecs/deployment/ecr/ - Output:
ecr_repository_name(used by subsequent jobs) - Terraform variables:
common.tfvars
-
retrieve-ssl (depends on: test-terraform-modules, deploy-terraform-state-bucket)
- Requests ACM certificate for root and wildcard domains
- Creates DNS validation records in Route 53
- Waits for certificate validation to complete (can take 5-30 minutes)
- Working directory:
infra-ecs/deployment/ssl/ - Terraform variables:
common.tfvars,domain.tfvars,backend.tfvars
-
build-and-push-app-docker-image-to-ecr (depends on: deploy-ecr)
- Sets ECR image tag:
${ECR_REPO_URL}:${ENVIRONMENT}-${GIT_SHA} - Logs in to AWS ECR using
amazon-ecr-loginaction - Builds NestJS application:
corepack enable,pnpm install,pnpm build - Builds Docker image:
docker build -t $ECR_APP_IMAGE -f Dockerfile . - Pushes image to ECR:
docker push $ECR_APP_IMAGE - Tag Format Example:
123456789012.dkr.ecr.eu-west-1.amazonaws.com/prod-app:prod-a1b2c3d
- Sets ECR image tag:
-
deploy-vpc (depends on: deploy-ecr)
- Creates VPC, subnets, NAT Gateway(s), Internet Gateway
- Working directory:
infra-ecs/deployment/app/vpc/ - Terraform variables:
common.tfvars - Uses official
terraform-aws-modules/vpc/awsmodule
-
deploy-ecs-cluster (depends on: deploy-vpc)
- Creates ECS cluster, Auto Scaling Group, Launch Template, Capacity Provider
- Creates IAM instance role and instance profile
- Creates cluster security group
- Working directory:
infra-ecs/deployment/app/ecs_cluster/ - Terraform variables:
common.tfvars,backend.tfvars
-
deploy-alb (depends on: retrieve-ssl, deploy-vpc, deploy-ecs-cluster)
- Creates Application Load Balancer, listeners, target group
- Attaches validated ACM certificate to HTTPS listener
- Creates ALB security group
- Working directory:
infra-ecs/deployment/app/alb/ - Terraform variables:
common.tfvars,backend.tfvars
-
deploy-ecs-service (depends on: deploy-ecr, build-and-push-app-docker-image-to-ecr, deploy-ecs-cluster, deploy-alb)
- Creates ECS task definition (with ECR image from step 5)
- Creates ECS service with load balancer integration
- Creates task execution role and ECS tasks security group
- Optionally creates task auto-scaling configuration
- Working directory:
infra-ecs/deployment/app/ecs_service/ - Terraform variables:
common.tfvars,backend.tfvars,-var="ecr_app_image=$ECR_APP_IMAGE"
-
deploy-routing (depends on: deploy-alb)
- Creates Route 53 A records (root and www) pointing to ALB
- Working directory:
infra-ecs/deployment/app/routing/ - Terraform variables:
common.tfvars,domain.tfvars,backend.tfvars - Uses
terraform plan -out=tfplan.binaryfor safety
env:
AWS_REGION: eu-west-1
TERRAFORM_VERSION: 1.10.3AWS_ACCESS_KEY_ID: AWS IAM user access keyAWS_SECRET_ACCESS_KEY: AWS IAM user secret key
Workflows: ecs-destroy-aws-infra.yaml and ecs-destroy-hosted-zone.yaml
Trigger: Manual (workflow_dispatch)
Purpose: Clean removal of all infrastructure in reverse dependency order
Destroys the application and core services.
Jobs Sequence:
-
destroy-ecs-service
- Retrieves ECS cluster name and service name from Terraform outputs
- Scales ECS service to 0 tasks:
aws ecs update-service --desired-count 0 - Waits for stability:
aws ecs wait services-stable(ensures tasks are drained) - Destroys ECS service resources with
terraform destroy - Working directory:
infra-ecs/deployment/app/ecs_service/ - Why Scale First: Ensures clean shutdown of running containers before destroying infrastructure
-
destroy-routing (depends on: destroy-ecs-service)
- Destroys Route 53 A records
- Working directory:
infra-ecs/deployment/app/routing/
-
destroy-alb (depends on: destroy-ecs-service)
- Destroys ALB, listeners, target group, and security group
- Working directory:
infra-ecs/deployment/app/alb/
-
destroy-ecs-cluster (depends on: destroy-ecs-service, destroy-alb)
- Destroys ECS cluster, ASG, Launch Template, Capacity Provider
- Destroys IAM roles and security groups
- Working directory:
infra-ecs/deployment/app/ecs_cluster/
-
destroy-ssl (depends on: destroy-alb)
- Destroys ACM certificate and validation records
- Working directory:
infra-ecs/deployment/ssl/
-
destroy-ecr (depends on: destroy-ecs-service, destroy-alb)
- Deletes all Docker images first:
aws ecr batch-delete-image --image-ids "$(aws ecr list-images ...)" - Destroys ECR repository with
terraform destroy - Working directory:
infra-ecs/deployment/ecr/ - Why Delete Images First: Terraform cannot destroy a non-empty ECR repository
- Deletes all Docker images first:
-
destroy-vpc (depends on: destroy-ecs-cluster, destroy-alb, destroy-ssl, destroy-ecr)
- Destroys VPC, subnets, NAT Gateway(s), Internet Gateway
- Working directory:
infra-ecs/deployment/app/vpc/
Destroys foundational DNS and state storage (run after destroy-aws-infra).
Jobs Sequence:
-
destroy-hosted-zone
- Destroys Route 53 Hosted Zone
- Working directory:
infra-ecs/deployment/hosted_zone/
-
destroy-terraform-state-bucket (depends on: destroy-hosted-zone)
- Deletes all Terraform state files:
aws s3 rm s3://${STATE_BUCKET_NAME} --recursive - Imports state bucket into local state:
terraform import aws_s3_bucket.terraform_state_bucket ${STATE_BUCKET_NAME} - Destroys S3 bucket with
terraform destroy - Working directory:
infra-ecs/deployment/backend/ - Why Import: The state bucket's state is stored in the bucket itself, so it must be imported locally before destruction
- Deletes all Terraform state files:
Return to: Main README | Prerequisites and Setup | AWS Resources Deep Dive