| name | ci-cd-pipelines |
| description | CI/CD pipeline patterns for this monorepo using GitHub Actions. Staging deploys to EC2 via Docker Compose + Nginx. Production deploys to EC2 via ALB + ASG rolling instance refresh. Infrastructure is managed separately via Terraform in the turbo-infrastructure repo. |
CI/CD Pipelines
Quick Reference
| Workflow | File | Trigger | Purpose |
|---|
| CI | ci.yml | Push/PR to dev/main | Lint, typecheck, test, drift gate |
| Deploy Staging | deploy-staging.yml | Push to dev | Build → ECR → EC2 Docker Compose |
| Deploy Production | deploy-production.yml | Push to main | Build → ECR → EC2 ALB + ASG instance refresh |
Branch Strategy
dev → CI checks + auto-deploy to EC2 staging (Docker Compose + Nginx)
main → CI checks + auto-deploy to EC2 production (ALB + ASG rolling refresh)
CI Workflow (ci.yml)
Runs quality checks on every push and PR:
jobs:
ci:
steps:
- pnpm install --frozen-lockfile
- pnpm typecheck
- pnpm lint
- pnpm format
Key patterns:
concurrency cancels in-progress runs on new pushes
paths-ignore skips docs-only changes
--frozen-lockfile ensures reproducible installs
Staging Workflow (deploy-staging.yml)
Deploys to a single EC2 instance using Docker Compose + host Nginx.
Flow
- CI passes (lint, typecheck, format)
- Validate required GitHub variables
- Build Docker images → push to ECR (sha + latest tags)
- SSH into EC2:
- Copy
docker-compose.staging.yml
- Generate nginx configs inline (web HTTP/HTTPS + API HTTP/HTTPS)
- Write
.env file with all app env vars
docker compose pull && up -d
- Detect SSL certs → apply correct nginx config
nginx -t && systemctl reload nginx
- Health checks (backend
/api/v1/health, web /)
- Auto-rollback on failure (restore previous
.env + restart)
Nginx Config Generation
Nginx configs are generated on the GitHub Actions runner using quoted heredocs (preserves $host, $remote_addr, etc.), then copied to EC2 via SCP:
- name: Copy deployment files to EC2
run: |
# Quoted heredoc preserves nginx variables like $host
cat > /tmp/nginx-configs/web.conf << 'NGINX_WEB'
server {
listen 80;
server_name __DOMAIN__;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
...
}
}
NGINX_WEB
# SCP to EC2
scp $SSH_OPTS /tmp/nginx-configs/*.conf $REMOTE:/opt/staging/nginx/
On EC2, the deploy script uses sed to replace __DOMAIN__ and detects SSL certs to pick HTTP vs HTTPS configs.
Required Variables (Staging)
Variables: AWS_REGION, PROJECT_NAME, ECR_REGISTRY, ECR_REPOSITORY_API, ECR_REPOSITORY_WEB, STG_EC2_HOST, NEXT_PUBLIC_APP_URL, NEXT_PUBLIC_API_BASE_URL, SANCTUM_STATEFUL_DOMAINS, SESSION_DOMAIN
Secrets: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, STG_EC2_SSH_KEY, APP_KEY, DATABASE_URL, CORS_ORIGINS, MAIL_USERNAME, MAIL_PASSWORD, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
Production Workflow (deploy-production.yml)
Deploys to EC2 via ALB + ASG rolling instance refresh.
Flow
- CI passes
- Validate required GitHub variables
- Build Docker images → push to ECR (sha + latest tags)
- Launch new EC2 instance from launch template (uses cloud-init to pull latest images)
- Wait for instance to pass health checks
- Register instance with ALB target group
- Trigger ASG instance refresh → rolling replacement of old instances
- Auto-rollback on failure
Required Variables (Production)
Variables: AWS_REGION, PROJECT_NAME, ECR_REGISTRY, ECR_REPOSITORY_API, ECR_REPOSITORY_WEB, ASG_NAME, LAUNCH_TEMPLATE_ID, NEXT_PUBLIC_APP_URL, NEXT_PUBLIC_API_BASE_URL
Secrets: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, APP_KEY, DATABASE_URL, CORS_ORIGINS, SANCTUM_STATEFUL_DOMAINS, MAIL_USERNAME, MAIL_PASSWORD, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
Docker Build Pattern
Both workflows use the same build pattern:
- name: Build and push image
run: |
docker buildx build \
--file apps/web/Dockerfile \
--build-arg NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL \
--cache-from type=local,src=/tmp/.buildx-cache \
--cache-to type=local,dest=/tmp/.buildx-cache-new,mode=max \
--push \
--tag $ECR_REGISTRY/$ECR_REPOSITORY_WEB:$IMAGE_TAG \
--tag $ECR_REGISTRY/$ECR_REPOSITORY_WEB:latest \
.
Pipeline Best Practices
- Immutable image tags — Always tag with
github.sha, not just latest
- Fail-fast validation — Check all required vars before costly build steps
- Layer caching — Use local buildx cache with cache rotation
- Concurrency control — Cancel in-progress deploys on new pushes
- Stability wait —
aws ecs wait services-stable after ECS deployment
- Auto-rollback — Previous task definition (prod) or
.env.rollback (staging)
- Inline templates — Generate task definitions and nginx configs in-workflow, no template files in repo