DEV Community

Cover image for Building Production-Grade Microservices on AWS ECS Fargate with GitLab CI/CD Automation
Sameer Khanal
Sameer Khanal

Posted on

Building Production-Grade Microservices on AWS ECS Fargate with GitLab CI/CD Automation

1. Introduction

Nowadays, applications become more and more based on microservices, because the promise is to scale them easily, deploy each of them independently, and so have a faster development cycle. The architecture is built on containerization as a primitive, enabling teams to isolate their workloads and deploy them uniformly across environments.
But with the growing number of services, deployment pipelines tend to become:

  • Slow (like updating all services on every commit)
  • Hard to manage (multiple pipelines for a single service)
  • This was causing problems (manual deployments, Docker images being inconsistent, etc.).

This is where AWS Elastic Container Service (ECS) Fargate and Amazon ECR, combined with GitLab CI/CD, form an amazing partnership.

In this guide, we will create an actual production-ready microservices deployment system with:

  • ECS Fargate for serverless containers
  • Five microservices: billing, client_app_api, driver_api, odmu_auth, odmu_contractual
  • Amazon ECR as an image registry for containers
  • ALB (Application Load Balancer) with multiple target groups
  • GitLab CI/CD with folder-based triggers
  • Terraform for infrastructure as code
  • CloudWatch for observability

By the end, you’ll have:
✔ Independent deployments per microservice
✔ Folder-based Automated CI/CD
✔ Production-grade ECS services
✔ Scalable, secure, monitored architecture
✔ Clean GitLab pipeline without any manual SSH or PM2

2. ARCHITECTURE:


Key Components

Component Purpose
ECS Fargate Serverless containers (no EC2 management)
ECR Private container registry
ALB Routes incoming traffic to the right microservice
CloudWatch Logs and metrics
Terraform Infrastructure provisioning
GitLab CI/CD Automated container build and deployment

Why ECS Fargate?

  • No EC2 provisioning or patching
  • Auto-scaling built-in
  • Secure (tasks get their own ENI inside VPC)
  • Ideal for microservices

3. Microservices Structure

Your repo is structured like this:

services/
 ├── billing/
 ├── client_app_api/
 ├── driver_api/
 ├── odmu_auth/
 └── odmu_contractual/
.gitlab-ci.yml
terraform/
Enter fullscreen mode Exit fullscreen mode

Each microservice has:

  • Its own Dockerfile
  • Its own business logic
  • Its own task definition
  • Its own ECS service

4. Dockerizing the Microservices

Example Dockerfile for client_app_api:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .
EXPOSE 3000

CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

Use different ports for each microservice:

Service              Port
billing              3001
client_app_api       3000
driver_api           3002
odmu_auth            3003
odmu_contractual     3004
Enter fullscreen mode Exit fullscreen mode

5. Creating ECR Repositories

Using a sample AWS account:

Account ID: 745812456855
Region: us-east-1

aws ecr create-repository --repository-name billing
aws ecr create-repository --repository-name client_app_api
aws ecr create-repository --repository-name driver_api
aws ecr create-repository --repository-name odmu_auth
aws ecr create-repository --repository-name odmu_contractual


Enter fullscreen mode Exit fullscreen mode

Build and Push (example: billing)

docker build -t billing ./services/billing

docker tag billing:latest \
  123456789012.dkr.ecr.us-east-1.amazonaws.com/billing:latest

aws ecr get-login-password --region us-east-1 \
  | docker login --username AWS \
  --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

docker push \
  123456789012.dkr.ecr.us-east-1.amazonaws.com/billing:latest

Enter fullscreen mode Exit fullscreen mode

6. ECS Fargate: Task Definition (Terraform)

Example for the billing microservice:

resource "aws_ecs_task_definition" "billing" {
  family                   = "billing-task"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "512"
  memory                   = "1024"

  execution_role_arn = "arn:aws:iam::123456789012:role/ecsTaskExecutionRole"

  container_definitions = jsonencode([
    {
      name  = "billing"
      image = "123456789012.dkr.ecr.us-east-1.amazonaws.com/billing:latest"

      portMappings = [{
        containerPort = 3001
        protocol      = "tcp"
      }]

      essential = true
    }
  ])
}
Enter fullscreen mode Exit fullscreen mode

Repeat for all microservices.

7. Application Load Balancer Routing

Each microservice gets a separate Target Group:

Service Target Group Name Path Pattern
billing tg-billing /billing/*
client_app_api tg-client /client/*
driver_api tg-driver /driver/*
odmu_auth tg-auth /auth/*
odmu_contractual tg-contract /contract/*

Terraform example:

resource "aws_lb_target_group" "billing" {
  name        = "tg-billing"
  port        = 3001
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = var.vpc_id
}
Enter fullscreen mode Exit fullscreen mode
  1. GitLab CI/CD: Folder-Based Deployments This is the most important section. Instead of running one giant pipeline or manually using SSH/PM2, you configure GitLab to only deploy the service that changed.
stages:
  - build
  - deploy

# ----------------------------
# Billing Microservice
# ----------------------------
build_billing:
  stage: build
  only:
    changes:
      - services/billing/**
  script:
    - docker build -t billing ./services/billing
    - docker tag billing:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/billing:latest
    - aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com
    - docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/billing:latest

deploy_billing:
  stage: deploy
  only:
    changes:
      - services/billing/**
  script:
    - aws ecs update-service \
        --cluster production-cluster \
        --service billing-service \
        --force-new-deployment

Enter fullscreen mode Exit fullscreen mode

9. Monitoring & Observability (CloudWatch)

Logs appear under:

/ecs/billing
/ecs/client_app_api
/ecs/driver_api
/ecs/odmu_auth
/ecs/odmu_contractual
Enter fullscreen mode Exit fullscreen mode

Create log groups:

aws logs create-log-group --log-group-name /ecs/billing
aws logs put-retention-policy --log-group-name /ecs/billing --retention-in-days 7
Enter fullscreen mode Exit fullscreen mode

CPU Alarm Example:

aws cloudwatch put-metric-alarm \
--alarm-name BillingCPUHigh \
--metric-name CPUUtilization \
--namespace AWS/ECS \
--statistic Average \
--period 300 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=ServiceName,Value=billing-service \
--evaluation-periods 2 \
--alarm-actions arn:aws:sns:us-east-1:123456789012:NotifyTeam

Enter fullscreen mode Exit fullscreen mode

10. Lessons Learned:

Here are some of the lessons I learned from building this system:
✔ Keep microservices fully isolated
The Docker images, task definitions and ALB routes need to be decoupled.
✔ Use folder-based CI/CD
It reduces deployment times dramatically.
✔ Choose ECR instead of using public registries
It integrates seamlessly with ECS.
✔ Allocate the right CPU & Memory
Under-provisioning leads to throttling.
Over-provisioning wastes money.
✔ Terraform modules save weeks
When multiple environment setups are in question, reusability is the key.
✔ You’ll get by with just CloudWatch for 80% of your monitoring
Later, add Prometheus or Grafana Cloud.

11. Conclusion

Containers are the foundation of modern infrastructure, and AWS ECS Fargate enables you to run microservices without managing servers. Mixed with GitLab CI/CD and Terraform, you have a scalable, automated, production-grade workflow.
This architecture ensures:

  • Fast, independent deployments
  • Secure and isolated workloads
  • ALB + Fargate for high availability
  • Complete observability via CloudWatch
  • Infrastructure is defined as code

Here’s a system that grows with you—from 5 microservices all the way to 50.
If you apply to AWS Community Builders, this post shows (with clear evidence):
✔ Practical hands-on AWS experience
✔ Experience with containers, CI/CD and IaC
✔ Design of production architects
✔ Real, actionable implementations

If you enjoyed this post, please share it on your favourite social network and get in touch if I can help with anything. Feel free to reach out if you’d like to chat about AWS, devops, or container architectures.

Top comments (0)