DEV Community

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

Posted on • Edited on

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

1. Introduction

Nowadays, applications are moving towards microservices because of their scalability, rollback deployment, and faster development.
But with the growing number of services, deployment pipelines have 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.).

I combined AWS Elastic Container Service (ECS) Fargate and Amazon ECR, combined with GitLab CI/CD, which made the pipeline deploy only the service which was changed.

In this guide, I have created 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 monitoring

By the end, you’ll have:

  • Independent deployments per microservice
  • Folder-based Automated CI/CD
  • Production grade ECS services
  • Scalable, secure, monitored architecture
  • No manual SSH or PM2

The Real Problem

When I was previously working with ECS, every change in code triggered a full redeployment of all services, although the change was in one service folder. It caused unnecessary downtime and more deployment times, and rollback was risky. I also faced Docker image drift in various environments.

I found a solution

  • Only the service that changes is rebuilt and deployed
  • Each service remains fully isolated
  • Deployments are repeatable and predictable

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
  • Suits for microservices

3. Microservices Structure

Repo Structure:

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

I used 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

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 \
  745812456855.dkr.ecr.us-east-1.amazonaws.com/billing:latest

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

docker push \
  745812456855.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::745812456855:role/ecsTaskExecutionRole"

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

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

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

Similar type for all microservices.

7. Application Load Balancer Routing

Each microservice has 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/*

Security Considerations

Security was a first-class concern in this design.

  • Each ECS task runs with VPC networking. Each ECS task has ENI-level network isolation.
  • Containers cannot be accessed via SSH; therefore, all deployments take place through CI/CD.
  • IAM task execution roles are created following the principle of least privilege.
  • All container images are stored in private ECR repositories on Amazon.
  • The ALB serves as the only public access point to ECS, and all services remain in a private subnet inside the VPC.

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, I configured the GitLab yml file 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 745812456855.dkr.ecr.us-east-1.amazonaws.com/billing:latest
    - aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 745812456855.dkr.ecr.us-east-1.amazonaws.com
    - docker push 745812456855.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:

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:745812456855:NotifyTeam

Enter fullscreen mode Exit fullscreen mode

Effects and Outcomes of Implementing New Architecture

By deploying this architecture I was able to:

  • Reduce deployment time, since only those services that had their code changed must be rebuilt.
  • Allow teams to deploy their microservices independently of one another.
  • No need to deploy via SSH using PM2.
  • Create a more predictable and safer rollback process.
  • Provide for reuse of the same Terraform modules in all target environments.

With the increasing number of services, it became clear that this architecture can be scaled successfully.

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 so much.
✔ Choose ECR instead of using public registries
It works well 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.
✔ With this CloudWatch setu you get 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 with Fargate for high availability
  • Complete observability via CloudWatch
  • Infrastructure is defined as code

If you are deploying microservices on AWS and experiencing slow or high risk deployments, this might be a good working solution validated across real-world environments with the increasing number of services.

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)