Skip to main content

Backend Deployment

The aiqlick-backend (NestJS) service deploys to AWS ECS Fargate via GitHub Actions. After Phase 7 the SSH-into-EC2 deploy is gone — GitHub Actions now talks to ECS APIs only.

End-to-end deploy time is ~3-5 minutes dominated by the Docker build (~1-2 min with warm BuildKit cache) and ECS rolling deployment (~2-3 min including health-check grace period). Schema-change deploys add ~30s for the one-shot prisma db push task.

Environments

EnvironmentECS serviceTask familyImage tagURL
Productionbackend-prod (cluster aiqlick)aiqlick-backend-prodaiqlick-backend:latesthttps://api.aiqlick.com
Developmentbackend-dev (cluster aiqlick)aiqlick-backend-devaiqlick-backend:dev-latesthttps://api-dev.aiqlick.com

Both serve through the shared aiqlick-public-alb with host-header rules (api.aiqlick.com → tg-backend-prod, api-dev.aiqlick.com → tg-backend-dev).

Deployment Pipeline

Workflow file: .github/workflows/aws-deploy.yml Runner: runs-on: ubuntu-latest (no self-hosted runner needed).

Branch-to-Environment Mapping

BranchImage tag prefixECS serviceSecret bundle
devdev-backend-devaiqlick-backend/development
main(none)backend-prodaiqlick-backend/production

Docker Build

3-stage multi-stage Dockerfile (unchanged from EC2):

PropertyValue
Builder imagenode:22-alpine
Runner imagenode:22-alpine
Build deps (builder only)python3, make, g++ (for bcrypt/sharp native modules)
Entrypointnode dist/src/main.js
Healthcheckwget --spider http://localhost:4001/health (30s interval)
Image size~240 MB
Build time~2 minutes (cold), ~30s (warm)

Schema changes (prisma db push)

When prisma/schema.prisma changes between commits, the workflow runs an intermediate one-shot Fargate task BEFORE the long-running service rolls. The task family is aiqlick-prisma-migrate-{env}, image is the new image just pushed, command is npx prisma db push, network config is copied from the long-running service so it reaches RDS through the same private subnets.

If prisma db push fails the workflow aborts — the long-running service stays on the old image, so the env keeps serving requests with the old code + old schema. Fix the schema and re-run.

Service deployment

aws-actions/amazon-ecs-deploy-task-definition@v2 registers a new revision of the service task definition with the new image, calls UpdateService, and waits for steady state (wait-for-service-stability: true, 10-min cap). The deployment circuit breaker auto-rolls back to the previous revision if the new tasks fail health checks.

External health verification

After ECS reports steady state, the workflow curls https://api(.dev).aiqlick.com/health and posts a { __typename } query to the GraphQL endpoint. Both must return 200.

Manual deployment

If CI is broken and you need to roll a new image manually:

# Resolve current image SHA in ECR
SHA=$(aws ecr describe-images --profile Administrator-842697652860 --region eu-north-1 \
--repository-name aiqlick-backend --query 'imageDetails[0].imageDigest' --output text)

# Force a new deployment of the existing task definition (re-pulls latest tag)
aws ecs update-service --profile Administrator-842697652860 --region eu-north-1 \
--cluster aiqlick --service backend-prod --force-new-deployment

# Wait for stability
aws ecs wait services-stable --profile Administrator-842697652860 --region eu-north-1 \
--cluster aiqlick --services backend-prod

To pin to a specific historical task-def revision:

aws ecs update-service --profile Administrator-842697652860 --region eu-north-1 \
--cluster aiqlick --service backend-prod \
--task-definition aiqlick-backend-prod:42

Troubleshooting

ProblemCommands
Tasks not startingaws ecs describe-services --cluster aiqlick --services backend-prod --query 'services[0].events[0:10]'
Container crash on bootaws logs tail /ecs/aiqlick-backend-prod --follow --since 10m
Health checks failingaws elbv2 describe-target-health --target-group-arn $(aws elbv2 describe-target-groups --names tg-backend-prod --query 'TargetGroups[0].TargetGroupArn' --output text)
Bad secret bundleaws ecs execute-command --cluster aiqlick --task ... --container aiqlick-backend --interactive --command 'sh' && env | grep -i secret