--- name: aws-cloudformation-task-ecs-deploy-gh description: Provides patterns to deploy ECS tasks and services with GitHub Actions CI/CD. Use when building Docker images, pushing to ECR, updating ECS task definitions, deploying ECS services, integrating with CloudFormation stacks, configuring AWS OIDC authentication for GitHub Actions, and implementing production-ready container deployment pipelines. Supports ECS deployments with proper security (OIDC or IAM keys), multi-environment support, blue/green deployments, ECR private repositories with image scanning, and CloudFormation infrastructure updates. category: aws tags: [aws, cloudformation, github-actions, ecs, ecr, deployment, ci-cd, oidc, containers, docker, cd] version: 1.0.0 allowed-tools: Read, Write, Bash --- # 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" ## Instructions Follow these steps to set up ECS deployment with GitHub Actions: 1. **Configure AWS Authentication**: Set up OIDC provider for GitHub Actions 2. **Create IAM Roles**: Define roles for deployment actions 3. **Set Up ECR Repository**: Create repository with image scanning 4. **Create ECS Cluster**: Define cluster infrastructure 5. **Configure Task Definition**: Set up task and container definitions 6. **Set Up ECS Service**: Configure service with deployment strategy 7. **Create GitHub Workflow**: Define CI/CD pipeline steps 8. **Configure Secrets**: Store credentials securely in GitHub Secrets For complete examples, see the [EXAMPLES.md](references/examples.md) file. ## Examples The following examples demonstrate common deployment patterns: ### Example 1: GitHub Actions Workflow ```yaml name: Deploy to ECS on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest permissions: contents: read id-token: write steps: - uses: actions/checkout@v4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole aws-region: us-east-1 - name: Login to Amazon ECR uses: aws-actions/amazon-ecr-login@v2 - name: Build, tag, 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 ``` ### Example 2: Update Task Definition ```yaml - name: Update ECS Task Definition id: task-def uses: aws-actions/amazon-ecs-render-task-definition@v1 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.task-def.outputs.task-definition }} service: my-service cluster: my-cluster ``` ### Example 3: OIDC Provider Setup ```yaml OidcProvider: Type: AWS::IAM::OIDCProvider Properties: Url: https://token.actions.githubusercontent.com ClientIdList: - sts.amazonaws.com ThumbprintList: - 6938fd4d98bab03faabd97ca34b7b5dbd06c4ee5 GitHubActionsRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !Ref OidcProvider Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com token.actions.githubusercontent.com:sub: repo:my-org/my-repo:ref:refs/heads/main ``` For complete production-ready examples, see [EXAMPLES.md](references/examples.md). ## Quick Start ### Basic ECS Deployment Workflow ```yaml 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:** 1. Create IAM role with trust policy for GitHub: ```yaml 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 ``` 2. Configure GitHub Actions workflow: ```yaml - 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) ```yaml - 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 ```yaml 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 ```yaml - 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:** ```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 ```yaml - 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) ```yaml - 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:** ```yaml 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 ```yaml - 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:** ```yaml version: 0.0 Resources: - TargetService: Type: AWS::ECS::Service Properties: TaskDefinition: LoadBalancerInfo: ContainerName: my-app ContainerPort: 8080 PlatformVersion: "1.4.0" ``` **CloudFormation CodeDeploy Configuration:** ```yaml 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 ```yaml - 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:** ```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 ```yaml 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 1. **Use OIDC authentication** instead of long-lived IAM keys 2. **Implement least privilege IAM roles** with specific permissions 3. **Enable ECR image scanning** on push 4. **Use AWS Secrets Manager** for sensitive data 5. **Encrypt ECR repositories** with KMS 6. **VPC endpoints** for ECR and ECS without internet gateway 7. **Security groups** restrict access to minimum required ### IAM Role Permissions ```yaml 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 1. **Docker layer caching** with GitHub Actions cache 2. **Multi-stage builds** to minimize image size 3. **Parallel deployments** across multiple environments 4. **Fargate Spot** for cost savings on non-critical workloads 5. **CloudWatch Logs** with appropriate retention policies ### Cost Optimization 1. **ECR lifecycle policies** to clean up old images 2. **Fargate Spot** instances for development/testing 3. **Right-sized task CPU and memory** 4. **Auto-scaling** based on metrics 5. **Scheduled scaling** for predictable traffic patterns ### Multi-Environment Deployments ```yaml 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 1. **CloudWatch Container Insights** for ECS metrics 2. **Application logs** centralized in CloudWatch Logs 3. **Health checks** with proper grace periods 4. **CloudWatch Alarms** for deployment failures 5. **X-Ray tracing** for distributed tracing ## Further Reading - [Reference Documentation](./reference.md) - Detailed technical reference for GitHub Actions syntax, OIDC configuration, ECR operations, task definitions, and CloudFormation integration - [Production Examples](./examples.md) - 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](../aws-cloudformation-ecs/) - Design and implement ECS cluster architecture - [aws-sdk-java-v2-ecs](../../aws-java/aws-sdk-java-v2-ecs/) - Programmatic ECS management with Java SDK - [aws-cloudformation](../aws-cloudformation/) - Core CloudFormation patterns and best practices ## Constraints and Warnings ### Resource Limits - **GitHub Actions Limits**: GitHub Actions has usage limits per account (minutes, storage) - **Workflow File Size**: Workflow files cannot exceed 1 MB in size - **Job Matrix Limits**: Matrix jobs have limits on total combinations - **Artifact Retention**: Artifacts and logs have default 90-day retention ### Authentication Constraints - **OIDC Tokens**: OIDC tokens have limited lifetime (typically 5 minutes) - **Role Session Duration**: IAM role session duration maximum is 12 hours - **Permission Scope**: GitHub OIDC role should have least-privilege permissions - **Cross-Account Access**: Cross-account deployments require appropriate trust relationships ### Operational Constraints - **Deployment Speed**: CloudFormation deployments may take time; don't assume instant completion - **Stack Locking**: Stacks cannot be updated while another deployment is in progress - **ECR Rate Limits**: ECR has API rate limits that may affect large deployments - **ECS Service Limits**: ECS has limits on tasks per service and services per cluster ### Security Constraints - **Secret Management**: Never store secrets in GitHub repository or workflow files - **OIDC Provider**: OIDC provider must be configured in AWS account before first use - **Repository Access**: Workflow secrets are scoped to repository; organization secrets differ - **Token Security**: GitHub tokens should have minimum required permissions (contents: read, id-token: write) ### Cost Considerations - **GitHub Actions**: Minutes beyond free tier incur costs based on runner type - **ECR Storage**: Container images stored in ECR incur monthly storage costs - **ECS/Fargate**: Fargate tasks incur costs for vCPU and memory resources - **Data Transfer**: Data transfer between GitHub runners and AWS resources incurs costs ### Deployment Constraints - **Stack Drift**: Manual changes to resources cause stack drift - **Change Set Limits**: Multiple change sets in progress can cause confusion - **Rollback Limits**: Failed deployments roll back by default; can be disabled - **Timeout Settings**: Deployment timeouts should account for application startup time ## Additional Files