AWS CI/CD Pipeline Setup: A Production-Ready Guide
Why AWS CI/CD Pipeline Setup Fails in Production
Most tutorials on AWS CI/CD pipeline setup stop at deploying a static index.html file to an S3 bucket. They use overly permissive IAM roles, skip VPC integration, and ignore rolling deployment zero-downtime requirements. When you try to push a containerized application to Amazon ECS or EKS with real database migrations and environment secrets, those basic examples fall apart.
At Techsolss, we routinely audit and refactor deployment pipelines for growing engineering teams. The most common bottlenecks aren't related to AWS services themselves, but to poor state management, unoptimized build caches, and brittle IAM permission boundaries. This guide walks through a production-grade CI/CD pipeline architecture using GitHub Actions for orchestration, AWS CodeBuild for compilation, and Amazon ECS for zero-downtime execution.
Choosing Your CI/CD Tooling on AWS
Before writing configuration files, you need to decide where your pipeline orchestration lives. You have two primary patterns:
- All-Native AWS: AWS CodePipeline, CodeBuild, and CodeDeploy.
- Hybrid (GitHub Actions + AWS): GitHub Actions handling code triggers and testing, with AWS CodeBuild or direct AWS CLI/SDK calls handling infrastructure and deployment.
For most teams, we recommend the Hybrid approach. Developers already live in GitHub. Forcing them to view logs inside the AWS CodePipeline console introduces unnecessary friction. GitHub Actions provides superior matrix testing, a richer ecosystem of community actions, and simpler secret management for multi-tenant repositories. We will use GitHub Actions to trigger an AWS CodeBuild project that securely builds and pushes our container image.
Step 1: IAM Trust Setup and Least-Privilege Roles
Never hardcode AWS credentials (AWSACCESSKEYID and AWSSECRETACCESSKEY) into GitHub secrets. If a repository is compromised, your entire AWS account is exposed. Instead, use OpenID Connect (OIDC) to allow GitHub Actions to assume an IAM role directly via temporary security tokens.
Create an IAM Identity Provider in your AWS console for GitHub:
- Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
Next, create an IAM role for GitHub Actions with a trust policy that restricts assumption to your specific GitHub organization and repository:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012: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:your-org/your-repo:*"
}
}
}
]
}
Attach a policy granting only the necessary permissions to push to Amazon Elastic Container Registry (ECR) and update your Amazon ECS service. If your architecture is shifting toward containerized microservices, ensuring your deployment pipelines scale properly often parallels considerations when evaluating broader infrastructure changes, such as when migrating a SaaS to Kubernetes.
Step 2: The Container Build and Push Workflow
Your application code needs to be packaged into a Docker image, tagged with the Git commit SHA, and pushed to ECR. Here is a production-tested GitHub Actions workflow (.github/workflows/deploy.yml) that authenticates via OIDC and triggers the build:
name: Production AWS Deploy
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
env:
AWS_REGION: us-east-1
ECR_REPOSITORY: my-app-prod
ECS_SERVICE: app-service
ECS_CLUSTER: production-cluster
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Configure AWS Credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeploymentRole
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Download Task Definition
run: |
aws ecs describe-task-definition --task-definition ${{ env.ECS_SERVICE }} --query taskDefinition > task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: render-task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: web
image: ${{ steps.build-image.outputs.image }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render-task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
Step 3: Handling Database Migrations Safely
One of the most dangerous anti-patterns in an AWS CI/CD pipeline setup is running database migrations inside the main web application container startup command (CMD or entrypoint.sh). If you scale your ECS service to 3 tasks simultaneously, all 3 tasks will attempt to run migrations concurrently, leading to race conditions, locked tables, and corrupted schemas.
Instead, execute database migrations as a separate ECS One-Off Task immediately before updating the main service task definition:
aws ecs run-task \
--cluster production-cluster \
--task-definition migration-task-def \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-12345],securityGroups=[sg-12345],assignPublicIp=ENABLED}"
Integrate this check into your pipeline after pushing the new image, ensuring the migration completes successfully (STOPPED with exit code 0) before proceeding with the rolling ECS service update.
Step 4: Monitoring, Rollbacks, and Health Checks
Setting up the pipeline is only half the battle; ensuring it fails gracefully when things break is what separates amateur setups from professional infrastructure.
- ECS Deployment Circuit Breaker: Always enable the rolling update circuit breaker on your ECS service configuration. If the new container version fails health checks repeatedly, ECS will automatically roll back to the previously stable task definition.
- Application Load Balancer (ALB) Target Group Health Checks: Ensure your health check path (e.g.,
/healthz) checks internal database connectivity and cache responsiveness, not just whether the HTTP server is running. - Pipeline Notifications: Route SNS topics from your deployment failures into Slack or PagerDuty channels so your on-call engineers have immediate visibility.
Whether you are setting up your first deployment pipeline or scaling your operations with specialized DevOps consulting services, getting the foundational automation right prevents countless midnight pager alerts.
Conclusion
A resilient AWS CI/CD pipeline setup relies on secure OIDC authentication, decoupled container builds, isolated database migrations, and native ECS deployment safeguards. By moving away from hardcoded credentials and brittle shell scripts, you create a repeatable deployment engine that scales with your engineering team. If you want an expert set of eyes on your architecture, contact Techsolss to discuss your deployment workflow.
FAQ
Should I use AWS CodePipeline or GitHub Actions for CI/CD on AWS?
For most modern engineering teams, a hybrid approach using GitHub Actions for workflow orchestration and code triggers—combined with AWS CodeBuild or direct AWS CLI commands for deployments—offers better flexibility, faster feedback loops, and simpler secret management.
How do I securely connect GitHub Actions to AWS without storing access keys?
You should use OpenID Connect (OIDC). By configuring an AWS IAM Identity Provider for GitHub, your GitHub Actions workflows can assume an IAM role using temporary security tokens, eliminating long-lived static credentials entirely.
How should database migrations be handled in an ECS CI/CD pipeline?
Database migrations should never run inside the startup script of your primary web service tasks, as parallel tasks will race and corrupt schemas. Instead, trigger a separate ECS one-off task to run migrations successfully before updating the main service task definition.
Related reading
[
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "AWS CI/CD Pipeline Setup: A Production-Ready Guide",
"author": {
"@type": "Person",
"name": "Muhammad Ramzan"
},
"publisher": {
"@type": "Organization",
"name": "Techsolss"
},
"datePublished": "2026-08-04",
"mainEntityOfPage": "https://techsolss.online/posts/aws-ci-cd-pipeline-setup-a-production-ready-guide.html"
},
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Should I use AWS CodePipeline or GitHub Actions for CI/CD on AWS?",
"acceptedAnswer": {
"@type": "Answer",
"text": "For most modern engineering teams, a hybrid approach using GitHub Actions for workflow orchestration and code triggers\u2014combined with AWS CodeBuild or direct AWS CLI commands for deployments\u2014offers better flexibility, faster feedback loops, and simpler secret management."
}
},
{
"@type": "Question",
"name": "How do I securely connect GitHub Actions to AWS without storing access keys?",
"acceptedAnswer": {
"@type": "Answer",
"text": "You should use OpenID Connect (OIDC). By configuring an AWS IAM Identity Provider for GitHub, your GitHub Actions workflows can assume an IAM role using temporary security tokens, eliminating long-lived static credentials entirely."
}
},
{
"@type": "Question",
"name": "How should database migrations be handled in an ECS CI/CD pipeline?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Database migrations should never run inside the startup script of your primary web service tasks, as parallel tasks will race and corrupt schemas. Instead, trigger a separate ECS one-off task to run migrations successfully before updating the main service task definition."
}
}
]
}
]
Need senior DevOps, MLOps, or Cloud Architecture expertise?
We help startups and fast-shipping teams build rock-solid cloud infrastructure, automate deployments, and deploy production AI pipelines without full-time agency overhead. Let's discuss your architecture on a free 20-minute strategy call.
Book a free 20-min call