CI/CD Pipeline for a Dockerized Full-Stack App: GitHub Actions to ECS
Production-ready GitHub Actions workflow that builds Docker images, pushes to ECR, and deploys to ECS Fargate—eliminating manual deploys and 11pm hotfixes.
CI/CD Pipeline for a Dockerized Full-Stack App: GitHub Actions to ECS
If you've manually SSH'd into a server for an 11pm hotfix, you already know why automating deployment matters. A solid CI/CD pipeline isn't optional—it's how you stop being your own bottleneck.
This guide walks through a production-ready GitHub Actions pipeline that builds Docker images, pushes them to Amazon ECR, and deploys to ECS Fargate.
What We're Deploying
A typical full-stack setup:
- Frontend: Next.js (containerized)
- Backend: FastAPI or Django (containerized)
- Database: PostgreSQL on RDS (uncontainerized—don't run stateful databases in ECS)
- Orchestration: ECS Fargate with two separate services
Both containers live in the same ECR namespace, tagged by service and git SHA.
Prerequisites
- An ECS cluster and task definitions (frontend, backend) already created
- Two ECR repositories:
myapp/frontend,myapp/backend - An IAM OIDC role with ECR push and ECS deploy permissions
- GitHub repository with secrets configured
Repository Structure
The GitHub Actions Workflow
OIDC Instead of Static Keys
We use role-to-assume rather than long-lived IAM keys. Static credentials rotated poorly (or not at all) are a genuine liability. OIDC lets GitHub assume a short-lived token—no credentials sitting in secrets.
Setup takes ~15 minutes: configure an OIDC identity provider in IAM and create a role trusting token.actions.githubusercontent.com. Worth it.
Image Tagging with Git SHA
Using github.sha as your tag gives traceability. Every deployment maps to an exact commit. When production breaks, you know what code is running and can rollback instantly.
Never use :latest on production images. It breaks your rollback path and makes debugging harder.
Task Definition File
The amazon-ecs-deploy-task-definition action needs a local JSON file. Generate it once:
Commit this file. The action updates the image URI and registers a new revision automatically.
Adding a Test Gate
A deployment pipeline without tests is just automated breakage. Add this job before build-and-deploy:
Make build-and-deploy depend on it:
Failed tests now block deployment. That's the point.
Environment-Specific Deployments
For staging vs. production, use branch or tag conditions. A common pattern: main deploys to staging; tags deploy to production:
Use conditionals on deploy steps to target the right cluster based on github.ref.
What You Get
This pattern handles what actually matters in production: auditability via SHA tagging, security via OIDC, and confidence via a mandatory test gate. It's simple enough to reason about at 2am when something breaks.
Natural next steps: Docker layer caching for faster builds, Slack notifications, and AWS Secrets Manager instead of environment variables in task definitions.
Get the basics working first. Optimize when you have evidence of what's slow.
Damian Hodgkiss
Senior Staff Engineer at Sumo Group, leading development of AppSumo marketplace. Technical solopreneur with 25+ years of experience building SaaS products.