AWS CloudFormation Task ECS Deploy with GitHub Actions
Comprehensive skill for deploying ECS containers using GitHub Actions CI/CD pipelines with CloudFormation infrastructure management.
Overview
Deploy containerized applications to Amazon ECS using GitHub Actions workflows. This skill covers the complete deployment pipeline: authentication with AWS (OIDC recommended), building Docker images, pushing to Amazon ECR, updating task definitions, and deploying ECS services. Integrate with CloudFormation for infrastructure-as-code management and implement production-grade deployment strategies.
When to Use
Use this skill when:
-
Deploying Docker containers to Amazon ECS
-
Setting up GitHub Actions CI/CD pipelines for AWS
-
Configuring AWS authentication for GitHub Actions (OIDC or IAM keys)
-
Building and pushing Docker images to Amazon ECR
-
Updating ECS task definitions dynamically
-
Implementing blue/green or rolling deployments
-
Managing CloudFormation stacks from CI/CD
-
Setting up multi-environment deployments (dev/staging/prod)
-
Configuring private ECR repositories with image scanning
-
Automating container deployment with proper security practices
Trigger phrases:
-
"Deploy to ECS with GitHub Actions"
-
"Set up CI/CD for ECS containers"
-
"Configure GitHub Actions for AWS deployment"
-
"Build and push Docker image to ECR"
-
"Update ECS task definition from CI/CD"
-
"Implement blue/green deployment for ECS"
-
"Deploy CloudFormation stack from GitHub Actions"
Quick Start
Basic ECS Deployment Workflow
name: Deploy to ECS on: push: branches: [main]
jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - name: Checkout uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecs-role
aws-region: us-east-1
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: my-app
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: Update task definition
uses: aws-actions/amazon-ecs-render-task-definition@v1
id: render-task
with:
task-definition: task-definition.json
container-name: my-app
image: ${{ steps.login-ecr.outputs.registry }}/my-app:${{ github.sha }}
- name: Deploy to ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.render-task.outputs.task-definition }}
service: my-service
cluster: my-cluster
wait-for-service-stability: true
Authentication Methods
OIDC Authentication (Recommended)
OpenID Connect (OIDC) provides secure, passwordless authentication between GitHub Actions and AWS.
Prerequisites:
- Create IAM role with trust policy for GitHub:
Type: AWS::IAM::Role Properties: RoleName: github-actions-ecs-role AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Federated: !Sub 'arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com' Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: token.actions.githubusercontent.com:sub: repo:my-org/my-repo:* ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonECS_FullAccess - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess
-
Configure GitHub Actions workflow:
-
name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecs-role aws-region: us-east-1
IAM Key Authentication (Legacy)
- name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1
Store credentials in GitHub repository secrets (Settings → Secrets and variables → Actions).
Build and Push to ECR
ECR Repository CloudFormation Template
ECRRepository: Type: AWS::ECR::Repository Properties: RepositoryName: my-app ImageScanningConfiguration: ScanOnPush: true ImageTagMutability: IMMUTABLE LifecyclePolicy: LifecyclePolicyText: | { "rules": [ { "rulePriority": 1, "description": "Keep last 30 images", "selection": { "tagStatus": "tagged", "tagPrefixList": ["v"], "countType": "imageCountMoreThan", "countNumber": 30 }, "action": { "type": "expire" } } ] }
Build and Push Step
-
name: Login to ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2
-
name: Set up Docker Buildx uses: docker/setup-buildx-action@v3
-
name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: | ${{ steps.login-ecr.outputs.registry }}/my-app:${{ github.sha }} ${{ steps.login-ecr.outputs.registry }}/my-app:latest cache-from: type=gha cache-to: type=gha,mode=max build-args: | BUILD_DATE=${{ github.event.head_commit.timestamp }} VERSION=${{ github.sha }}
Task Definition Management
Basic Task Definition
task-definition.json:
{ "family": "my-app-task", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "256", "memory": "512", "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole", "taskRoleArn": "arn:aws:iam::123456789012:role/ecsTaskRole", "containerDefinitions": [ { "name": "my-app", "image": "PLACEHOLDER_IMAGE", "portMappings": [ { "containerPort": 8080, "protocol": "tcp" } ], "environment": [ { "name": "ENVIRONMENT", "value": "production" } ], "secrets": [ { "name": "DB_PASSWORD", "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-app/db-password" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/my-app", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "ecs", "awslogs-create-group": "true" } } } ] }
Dynamic Task Definition Update
-
name: Render task definition uses: aws-actions/amazon-ecs-render-task-definition@v1 id: render-task with: task-definition: task-definition.json container-name: my-app image: ${{ steps.login-ecr.outputs.registry }}/my-app:${{ github.sha }}
-
name: Deploy task definition uses: aws-actions/amazon-ecs-deploy-task-definition@v1 with: task-definition: ${{ steps.render-task.outputs.task-definition }} service: my-service cluster: my-cluster wait-for-service-stability: true deploy_timeout: 30 minutes
ECS Deployment Strategies
Rolling Deployment (Default)
- name: Deploy to ECS (Rolling) uses: aws-actions/amazon-ecs-deploy-task-definition@v1 with: task-definition: ${{ steps.render-task.outputs.task-definition }} service: my-service cluster: my-cluster wait-for-service-stability: true
CloudFormation Service Configuration:
ECSService: Type: AWS::ECS::Service Properties: ServiceName: my-service Cluster: !Ref ECSCluster TaskDefinition: !Ref TaskDefinition DeploymentConfiguration: MaximumPercent: 200 MinimumHealthyPercent: 100 DeploymentCircuitBreaker: Enable: true Rollback: true HealthCheckGracePeriodSeconds: 60 EnableECSManagedTags: true PropagateTags: SERVICE
Blue/Green Deployment with CodeDeploy
- name: Deploy to ECS (Blue/Green) uses: aws-actions/amazon-ecs-deploy-task-definition@v1 with: task-definition: ${{ steps.render-task.outputs.task-definition }} service: my-service cluster: my-cluster codedeploy-appspec: appspec.yaml codedeploy-application: my-app codedeploy-deployment-group: my-deployment-group wait-for-service-stability: true
appspec.yaml:
version: 0.0 Resources:
- TargetService: Type: AWS::ECS::Service Properties: TaskDefinition: <TASK_DEFINITION> LoadBalancerInfo: ContainerName: my-app ContainerPort: 8080 PlatformVersion: "1.4.0"
CloudFormation CodeDeploy Configuration:
CodeDeployApplication: Type: AWS::CodeDeploy::Application Properties: ApplicationName: my-app ComputePlatform: ECS
CodeDeployDeploymentGroup: Type: AWS::CodeDeploy::DeploymentGroup Properties: ApplicationName: !Ref CodeDeployApplication DeploymentGroupName: my-deployment-group ServiceRoleArn: !Ref CodeDeployServiceRole DeploymentConfigName: CodeDeployDefault.ECSAllAtOnce DeploymentStyle: DeploymentType: BLUE_GREEN DeploymentOption: WITH_TRAFFIC_CONTROL AutoRollbackConfiguration: Enabled: true Events: - DEPLOYMENT_FAILURE - DEPLOYMENT_STOP_ON_ALARM - DEPLOYMENT_STOP_ON_REQUEST AlarmConfiguration: Alarms: - !Ref CPUPercentageAlarm - !Ref MemoryPercentageAlarm BlueGreenDeploymentConfiguration: TerminateBlueInstancesOnDeploymentSuccess: Action: TERMINATE WaitTimeInMinutes: 5 DeploymentReadyOption: ActionOnTimeout: CONTINUE_DEPLOYMENT WaitTimeInMinutes: 0 LoadBalancerInfo: TargetGroupPairInfoList: - TargetGroups: - Ref: BlueTargetGroup - Ref: GreenTargetGroup ProdTrafficRoute: ListenerArns: - !Ref ProductionListener
CloudFormation Integration
Update Stack from GitHub Actions
- name: Deploy CloudFormation stack
run: |
aws cloudformation deploy
--template-file infrastructure/ecs-stack.yaml
--stack-name my-app-ecs
--capabilities CAPABILITY_NAMED_IAM
--parameter-overrides
Environment=production
DesiredCount=2
CPU=256
Memory=512
ImageUrl=${{ steps.login-ecr.outputs.registry }}/my-app:${{ github.sha }}
CloudFormation Stack with ECS Resources
ecs-stack.yaml:
AWSTemplateFormatVersion: '2010-09-09' Description: ECS Fargate Service with CloudFormation
Parameters: Environment: Type: String AllowedValues: [dev, staging, prod] DesiredCount: Type: Number Default: 2 CPU: Type: String Default: '256' Memory: Type: String Default: '512' ImageUrl: Type: String
Resources: ECSCluster: Type: AWS::ECS::Cluster Properties: ClusterName: !Sub '${Environment}-cluster' ClusterSettings: - Name: containerInsights Value: enabled
TaskDefinition: Type: AWS::ECS::TaskDefinition Properties: Family: !Sub '${Environment}-task' Cpu: !Ref CPU Memory: !Ref Memory NetworkMode: awsvpc RequiresCompatibilities: - FARGATE ExecutionRoleArn: !GetAtt TaskExecutionRole.Arn TaskRoleArn: !GetAtt TaskRole.Arn ContainerDefinitions: - Name: my-app Image: !Ref ImageUrl PortMappings: - ContainerPort: 8080 LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref LogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: ecs
ECSService: Type: AWS::ECS::Service DependsOn: LoadBalancerListener Properties: ServiceName: !Sub '${Environment}-service' Cluster: !Ref ECSCluster TaskDefinition: !Ref TaskDefinition DesiredCount: !Ref DesiredCount LaunchType: FARGATE NetworkConfiguration: AwsvpcConfiguration: Subnets: - !Ref PrivateSubnetA - !Ref PrivateSubnetB SecurityGroups: - !Ref ContainerSecurityGroup AssignPublicIp: DISABLED LoadBalancers: - ContainerName: my-app ContainerPort: 8080 TargetGroupArn: !Ref TargetGroup DeploymentConfiguration: MaximumPercent: 200 MinimumHealthyPercent: 100 DeploymentCircuitBreaker: Enable: true Rollback: true HealthCheckGracePeriodSeconds: 60
Outputs: ServiceURL: Description: Service URL Value: !Sub 'http://${LoadBalancer.DNSName}'
Stack Outputs for Cross-Stack References
Outputs: ClusterName: Description: ECS Cluster Name Value: !Ref ECSCluster Export: Name: !Sub '${AWS::StackName}-ClusterName'
ServiceName: Description: ECS Service Name Value: !Ref ECSService Export: Name: !Sub '${AWS::StackName}-ServiceName'
TaskDefinitionArn: Description: Task Definition ARN Value: !Ref TaskDefinition Export: Name: !Sub '${AWS::StackName}-TaskDefinitionArn'
Best Practices
Security
-
Use OIDC authentication instead of long-lived IAM keys
-
Implement least privilege IAM roles with specific permissions
-
Enable ECR image scanning on push
-
Use AWS Secrets Manager for sensitive data
-
Encrypt ECR repositories with KMS
-
VPC endpoints for ECR and ECS without internet gateway
-
Security groups restrict access to minimum required
IAM Role Permissions
ECSDeployRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Federated: !Sub 'arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com' Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: token.actions.githubusercontent.com:sub: repo:${GitHubOrg}/${GitHubRepo}:* Policies: - PolicyName: ECSDeployPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - ecs:DescribeServices - ecs:DescribeTaskDefinition - ecs:DescribeTasks - ecs:ListTasks - ecs:RegisterTaskDefinition - ecs:UpdateService Resource: !Sub 'arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:' - Effect: Allow Action: - ecr:GetAuthorizationToken - ecr:BatchCheckLayerAvailability - ecr:GetDownloadUrlForLayer - ecr:GetRepositoryPolicy - ecr:DescribeRepositories - ecr:ListImages - ecr:DescribeImages - ecr:BatchGetImage - ecr:InitiateLayerUpload - ecr:UploadLayerPart - ecr:CompleteLayerUpload - ecr:PutImage Resource: !Sub 'arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/${ECRRepositoryName}' - Effect: Allow Action: - cloudformation:DescribeStacks - cloudformation:CreateStack - cloudformation:UpdateStack - cloudformation:DescribeStackEvents Resource: !Sub 'arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${CloudFormationStackName}/'
Performance
-
Docker layer caching with GitHub Actions cache
-
Multi-stage builds to minimize image size
-
Parallel deployments across multiple environments
-
Fargate Spot for cost savings on non-critical workloads
-
CloudWatch Logs with appropriate retention policies
Cost Optimization
-
ECR lifecycle policies to clean up old images
-
Fargate Spot instances for development/testing
-
Right-sized task CPU and memory
-
Auto-scaling based on metrics
-
Scheduled scaling for predictable traffic patterns
Multi-Environment Deployments
name: Deploy to ECS on: push: branches: [main, staging, develop]
jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read strategy: matrix: environment: [dev, staging, prod] steps: - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecs-${{ matrix.environment }} aws-region: us-east-1
- name: Deploy to ${{ matrix.environment }}
run: |
aws cloudformation deploy \
--template-file infrastructure/ecs-stack.yaml \
--stack-name my-app-${{ matrix.environment }} \
--parameter-overrides \
Environment=${{ matrix.environment }} \
ImageUrl=${{ steps.build-image.outputs.image-url }}
Monitoring and Observability
-
CloudWatch Container Insights for ECS metrics
-
Application logs centralized in CloudWatch Logs
-
Health checks with proper grace periods
-
CloudWatch Alarms for deployment failures
-
X-Ray tracing for distributed tracing
Further Reading
-
Reference Documentation - Detailed technical reference for GitHub Actions syntax, OIDC configuration, ECR operations, task definitions, and CloudFormation integration
-
Production Examples - Complete, production-ready workflows including basic deployments, multi-environment setups, blue/green deployments, private ECR with scanning, CloudFormation stack updates, and full CI/CD pipelines with testing
Key Concepts
-
GitHub Actions: Automate workflows from GitHub repository events
-
OIDC Authentication: Secure, tokenless authentication between GitHub and AWS
-
ECR: Amazon Elastic Container Registry for Docker image storage
-
ECS: Amazon Elastic Container Service for container orchestration
-
Fargate: Serverless compute engine for ECS without managing servers
-
Task Definition: Blueprint for ECS containers (similar to Pod spec in Kubernetes)
-
Service: Long-running ECS service with load balancing and auto-scaling
-
CloudFormation: Infrastructure as Code for AWS resource management
-
Blue/Green Deployment: Zero-downtime deployment strategy with CodeDeploy
Common Troubleshooting
Authentication failures:
-
Verify OIDC trust relationship matches GitHub organization/repository
-
Check IAM role has proper permissions for ECR and ECS
-
Ensure GitHub Actions repository has id-token: write permission
Deployment failures:
-
Check CloudWatch Logs for application errors
-
Verify task definition matches service requirements
-
Ensure sufficient CPU/memory in Fargate cluster
-
Review health check configuration
ECR push failures:
-
Verify repository exists and permissions are correct
-
Check image tag format and registry URL
-
Ensure Docker daemon is running in GitHub Actions runner
-
Verify image size doesn't exceed ECR limits
CloudFormation rollback:
-
Review stack events in AWS Console
-
Check parameter values match resource constraints
-
Verify IAM role has cloudformation:UpdateStack permission
-
Enable termination protection for production stacks
Related Skills
-
aws-cloudformation-ecs - Design and implement ECS cluster architecture
-
aws-sdk-java-v2-ecs - Programmatic ECS management with Java SDK
-
aws-cloudformation - Core CloudFormation patterns and best practices