Introduction
Deploying containerized microservices manually to Amazon ECS (Elastic Container Service) requires orchestrating multiple infrastructure components, including VPC subnets, Application Load Balancers (ALB), Target Groups, IAM Execution Roles, and Task Definitions. Consequently, this operational complexity often slows down developer velocity. AWS Copilot CLI solves this challenge by providing an opinionated abstraction layer that provisions production-ready, serverless AWS Fargate infrastructure using simple CLI commands.
In this comprehensive hands-on tutorial, we will containerize a Python Flask web service using Docker. Furthermore, we will leverage AWS Copilot to deploy our container to ECS Fargate behind an ALB, inspect live container stream logs, and establish an automated CI/CD deployment pipeline using AWS CodePipeline and GitHub.
Prerequisites
Before we dive in, ensure you have:
- Docker installed
- AWS CLI configured with the right IAM permissions
- AWS Copilot CLI installed (brew install aws/tap/copilot or use binary)
- GitHub account (for CI/CD integration)
Step 1: Build Flask Application & Define Container Manifests
First, establish a clean directory structure and build a simple Python Flask API payload:
- Create a project directory and build your application entrypoint file (
app/app.py):
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route('/', methods=['GET'])
def health_check():
return jsonify({
"status": "healthy",
"message": "Hello from AWS ECS Fargate deployed via Copilot!",
"environment": os.getenv("COPILOT_ENVIRONMENT", "local")
}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
- Next, define application dependencies inside
app/requirements.txt:
Flask==3.0.3
gunicorn==22.0.0
- Now, create a optimized, production-grade
Dockerfileinside the root directory:
FROM python:3.11-slim
WORKDIR /app
COPY app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ .
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
- Test your Docker container build locally before pushing to cloud repositories:
docker build -t flask-ecs-app .
docker run -p 5000:5000 flask-ecs-app
Step 2: Install AWS Copilot CLI Across Platforms
Next, install the AWS Copilot binary interface onto your local execution environment:
- macOS (Homebrew):
brew install aws/tap/copilot-cli
- Linux (Direct Binary Package):
curl -Lo copilot https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux \
&& chmod +x copilot && sudo mv copilot /usr/local/bin/
- Windows (PowerShell):
Invoke-WebRequest -OutFile 'C:\copilot\copilot.exe' https://github.com/aws/copilot-cli/releases/latest/download/copilot-windows.exe
Step 3: Initialize Copilot App, Service, and Fargate Infrastructure
After configuring AWS CLI credentials, initialize your serverless ECS infrastructure interactively using Copilot:
- Execute the initialization sequence inside your project root folder:
copilot init
- Complete the interactive command prompt responses:
- Application Name:
flask-copilot-app - Workload Type:
Load Balanced Web Service(Deploys an ALB in front of Fargate) - Service Name:
web - Dockerfile Path:
./Dockerfile - Port:
5000 - Deploy to ‘test’ environment now? Select
Yes
- Application Name:
- Consequently, Copilot will automatically build your Docker image, provision an Amazon ECR repository, create a VPC with public/private subnets, launch an ECS Cluster, configure Target Group health checks, and bind an Application Load Balancer!
Step 4: Monitor Fargate Service Status & Live Tail Logs
Once deployment finishes, Copilot outputs the live ALB DNS endpoint (e.g., [http://web-publi-123456789.ap-south-1.elb.amazonaws.com](http://web-publi-123456789.ap-south-1.elb.amazonaws.com)). Verify service health and stream runtime logs:
- Inspect current service tasks and health metrics:
copilot svc status --name web
- Stream live stdout/stderr application logs directly from Fargate containers:
copilot svc logs --name web --follow
Step 5: Configure Automated CI/CD Continuous Deployment Pipeline
Finally, establish an automated build and delivery pipeline that triggers whenever new code commits land on GitHub:
- Initialize the pipeline manifest configuration:
copilot pipeline init
- Enter your GitHub repository details (
github-user/repository-name) and select your tracking branch (main). - Commit the generated
copilot/pipeline.ymlandcopilot/buildspec.ymlmanifests into Git:
git add copilot/
git commit -m "feat: add Copilot CI/CD pipeline manifests"
git push -u origin main
- Deploy the AWS CodePipeline infrastructure stack:
copilot pipeline deploy
- Consequently, any future
git pushto yourmainbranch will automatically trigger AWS CodeBuild, rebuild your container image, and execute zero-downtime rolling updates on ECS Fargate!
Enterprise Container Security & Operational Best Practices:
- Always Use Production WSGI Servers: Never execute Flask’s built-in development server (
app.run()) inside Docker containers in production. Always utilize multi-threaded WSGI servers like Gunicorn or uWSGI as defined in our Dockerfile execution vector.
- Zero-Downtime Rolling Deployments: Copilot automatically configures ECS task rolling deployments. When updating application code, new tasks are provisioned and pass health checks before old containers are drained and terminated.
Production Troubleshooting: Common Copilot & ECS Fargate Errors
Serverless container deployments rely on health checks and IAM policies. Use the diagnostic matrix below to resolve deployment issues fast:
Error 1: Task Failed Health Checks and Was Terminated
- The Error Log (CloudWatch / Copilot Logs):
[ESSENTIAL] Container exited with status code 1: Task failed ALB health checks on port 5000.
- The Root Cause: Gunicorn or Flask is listening on
127.0.0.1(localhost inside container) instead of binding to0.0.0.0(all network interfaces), preventing the Application Load Balancer from reaching the application interface. - The Fix: Ensure your WSGI command or application startup binds explicitly to
0.0.0.0:5000inside yourDockerfileandapp.py.
Error 2: CannotPullContainerError During ECS Task Launch
- The Error Log (ECS Event Stream):
CannotPullContainerError: Access denied to Amazon ECR repository or image tag does not exist.
- The Root Cause: The ECS Task Execution IAM Role is missing required
ecr:BatchGetImageorecr:GetDownloadUrlForLayerpermissions, or Fargate tasks in private subnets cannot reach ECR due to missing NAT Gateways. - The Fix: Ensure your Copilot environment manifest contains public subnet routing or NAT Gateways for private tasks, and verify that Copilot automatically managed the ECR resource policies during
copilot init.
Error 3: Copilot Pipeline Build Fails During CodeBuild Phase
- The Error Log (CodeBuild Log Output):
[Container] Phase context status code: COMMAND_EXECUTION_ERROR Message: Error building Docker image.
- The Root Cause: Missing Docker build context paths or syntax errors inside the project
Dockerfilewhen executed within the automated AWS CodeBuild environment. - The Fix: Run
docker build -t test-build .locally to verify build health before committing manifest changes to GitHub.Multi Copy Code Blocks




