Look, the idea is simple: push to main, magic happens, your app runs in production. Reality is messier.
The Happy Path That Never Happens
You push code. GitHub Actions kicks off a workflow. Docker builds your image. ECR stores it. ECS deploys it. Your users are happy. You sleep through the night.
Here's what actually happens when you first set this up:
Week 1: Your Docker build fails because you forgot to add node_modules
to .dockerignore
and your image is 2GB. GitHub Actions times out after 6 hours trying to push it.
Week 2: Build works, but your ECS task dies immediately with exit code 1. The logs show "Error: Cannot find module 'express'" because your multi-stage build is too clever and deleted the wrong dependencies.
Week 3: App runs but can't connect to the database. Your task definition has the wrong security group. You spend 4 hours learning that ECS networking is about as intuitive as quantum physics.
Docker: The Part That Should Be Easy But Isn't
Multi-stage builds are great in theory. They reduce image size from 1.5GB to 200MB. They also introduce a dozen new ways to break your dependencies.
## This looks clean but will bite you
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
## Your app works locally but breaks here
FROM node:18-alpine AS production
COPY --from=builder /app/node_modules ./node_modules
COPY . .
CMD ["npm", "start"]
Pro tip: `npm ci --only=production` doesn't install devDependencies, which breaks builds that need TypeScript or other build tools. You'll discover this at 11pm when your supposedly "production-ready" image crashes because TypeScript isn't installed.
ECS: Where Your Deployment Goes to Die
ECS task definitions are XML-level verbose. A simple Node.js app needs 150+ lines of JSON to define CPU (256-4096 units, because apparently AWS engineers hate round numbers), memory (must be specific combinations or ECS throws a tantrum), and networking (good luck).
Real talk: Fargate costs 3x more than EC2 but saves your sanity. You'll pay the premium after spending a weekend debugging why your container can't resolve DNS on a custom EC2 cluster.
GitHub Actions: The Good News
The only part of this stack that doesn't hate you. Actions for AWS are actually well-maintained. OIDC authentication works. The official actions don't randomly break.
But here's what nobody tells you: your first workflow will take 45 minutes to run because Docker layer caching is disabled by default and you're rebuilding everything from scratch every time.
Where The Money Goes
GitHub Actions charges $0.008/minute. Sounds cheap until you realize your inefficient Docker builds consume 15 minutes per deployment. That's $3.60 per deploy. Deploy 10 times a day and suddenly you're paying $100+ monthly just for CI.
ECR costs sneak up on you. $0.10/GB/month sounds reasonable until you accumulate 50 old images because you didn't set up lifecycle policies. Your "free" container registry costs $60/month.
Fargate pricing is $0.04048/vCPU/hour. A small app (0.25 vCPU, 512MB RAM) costs $7.20/month if it runs 24/7. Scale to handle real traffic and you're looking at $100+/month just for compute.
The Real Architecture
Here's what actually happens in production:
- Developer pushes to main at 5:47pm on Friday (why do we do this to ourselves?)
- GitHub Actions starts. Build time: 12 minutes because someone added a 500MB dependency
- ECR image scan finds 47 "critical" vulnerabilities in base OS packages you can't control
- Deployment succeeds but health checks fail. Task keeps restarting
- You debug for 2 hours, discover the container port is 3000 but load balancer expects 80
- Fix that, redeploy. Health checks pass but users get 500 errors
- Turns out your database connection string is wrong. Environment variable was
DATABASE_URL
, your code expectsDB_URL
- By 8:30pm everything works. You promise yourself you'll never deploy on Friday again
- You deploy on Friday again next week
The good news? Once it works, it really works. The bad news? Getting there requires sacrificing several weekends to the AWS documentation gods.