ZUA.
Home/Blog/ECS Deployment
Cloud InfrastructureMay 20, 2026 · 10 min read

Deploying Node.js Microservices on AWS ECS with Docker and CI/CD

A complete walkthrough: Dockerfile → ECR → ECS task definition → Fargate service → GitHub Actions pipeline for zero-downtime rolling deployments.

AWS ECSDockerNode.jsFargateECRGitHub ActionsCI/CD

Why ECS Fargate Over EC2?

ECS Fargate removes the need to manage EC2 instances entirely. You define a task (CPU + memory + container image) and AWS handles the underlying compute. For microservices, this means:

  • No patching, no SSH, no autoscaling groups to configure
  • Per-task billing — idle services cost nothing
  • Native integration with ALB, CloudWatch, Secrets Manager, and IAM roles
  • Rolling deployments with zero downtime out of the box

Step 1 — Dockerfile for Node.js

Use a multi-stage build to keep the production image lean.

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/node_modules ./node_modules
COPY . .
USER appuser
EXPOSE 3000
CMD ["node", "dist/main.js"]

Build and test locally before pushing:

docker build -t my-service:latest .
docker run -p 3000:3000 --env-file .env my-service:latest

Step 2 — Push to Amazon ECR

ECR is AWS's private container registry — it integrates natively with ECS and IAM.

aws ecr create-repository --repository-name my-service --region ap-south-1

aws ecr get-login-password --region ap-south-1 | \
  docker login --username AWS --password-stdin \
  123456789.dkr.ecr.ap-south-1.amazonaws.com

docker tag my-service:latest \
  123456789.dkr.ecr.ap-south-1.amazonaws.com/my-service:latest
docker push 123456789.dkr.ecr.ap-south-1.amazonaws.com/my-service:latest

Step 3 — ECS Task Definition

A task definition is the blueprint for your container — CPU, memory, image, environment, and IAM role.

{
  "family": "my-service",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "executionRoleArn": "arn:aws:iam::123456789:role/ecsTaskExecutionRole",
  "containerDefinitions": [{
    "name": "my-service",
    "image": "123456789.dkr.ecr.ap-south-1.amazonaws.com/my-service:latest",
    "portMappings": [{ "containerPort": 3000, "protocol": "tcp" }],
    "secrets": [{
      "name": "DATABASE_URL",
      "valueFrom": "arn:aws:secretsmanager:ap-south-1:123456789:secret:prod/db-url"
    }],
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "/ecs/my-service",
        "awslogs-region": "ap-south-1",
        "awslogs-stream-prefix": "ecs"
      }
    },
    "healthCheck": {
      "command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
      "interval": 30, "timeout": 5, "retries": 3
    }
  }]
}
aws ecs register-task-definition --cli-input-json file://task-definition.json

Step 4 — ECS Service with ALB

Create the service with a rolling deployment config. minimumHealthyPercent=100 + maximumPercent=200 means new tasks start before old ones stop — zero downtime.

aws ecs create-service \
  --cluster production \
  --service-name my-service \
  --task-definition my-service:1 \
  --desired-count 2 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-abc],securityGroups=[sg-xyz],assignPublicIp=DISABLED}" \
  --load-balancers "targetGroupArn=arn:aws:...,containerName=my-service,containerPort=3000" \
  --deployment-configuration "minimumHealthyPercent=100,maximumPercent=200"

Step 5 — GitHub Actions CI/CD Pipeline

Automate the full build → push → deploy cycle on every push to main.

# .github/workflows/deploy.yml
name: Deploy to ECS
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-south-1

      - name: Login to ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, tag, and push image
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/my-service:$IMAGE_TAG .
          docker push $ECR_REGISTRY/my-service:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/my-service:$IMAGE_TAG" >> $GITHUB_OUTPUT

      - name: Update ECS task definition
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: my-service
          image: ${{ steps.build-image.outputs.image }}

      - name: Deploy to ECS
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service: my-service
          cluster: production
          wait-for-service-stability: true

Production Checklist

  • Never put secrets in environment variables in the task definition — use Secrets Manager
  • Tag images with git SHA, not 'latest' — makes rollbacks trivial
  • Enable Container Insights in CloudWatch for CPU/memory metrics per task
  • Set up CloudWatch alarms on 5xx ALB errors to catch broken deployments
  • Use a VPC with private subnets — Fargate tasks should never have public IPs
  • Add a /health endpoint that returns 200 — ECS uses it to determine task health

Wrapping Up

This pipeline — multi-stage Docker build, ECR, ECS Fargate task definition with Secrets Manager, and GitHub Actions — is the setup I use for production microservices. Fully serverless on the infrastructure side and zero-downtime from day one.

Next up: auto-scaling ECS services based on ALB request count and SQS queue depth.

Need your services deployed on AWS ECS?

I build and ship production systems — happy to discuss your requirements.

Book a Call