Deep Dive & Step-By-Step: Deploying a Flask App to Amazon ECS with GitHub Actions & OpenID Connect

Modern developers want fast CI/CD but without storing long-lived AWS keys.By combining Amazon ECS (Fargate), Amazon ECR, GitHub Actions, and OpenID Connect (OIDC) you can deploy securely and automatically.
This blog explains how to deploy a containerized app to ECS with GitHub Actions CI/CD and why OIDC is the modern, secure way to let your pipeline assume AWS IAM roles.
System Architecture
Step 1 : Build and Containerize the App
Why: ECS runs containers; your app must be packaged into one. So, lets create minimal Flask app.
#app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "🚀 Hello from my-app on Amazon ECS using Github actions auto deployment!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=80)
#requirements.txt
flask
#Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 80
CMD ["python", "app.py"]
Step 2 : Set Up AWS Infrastructure
We need a place to store images (ECR), a cluster to run them (ECS), and a network.
2.1 Create ECR Repository
aws ecr create-repository --repository-name my-app
Gives a URI like:
XXXXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/my-app
ECR is a private Docker registry. ECS will pull images from here.
2.2 Create ECS Cluster
aws ecs create-cluster --cluster-name my-app-cluster --region <aws-region>
A cluster is just a logical group of capacity (in Fargate you don’t manage servers).
2.3 Define Task
Create ecs-task-def.json:
{
"family": "my-app-task",
"executionRoleArn": "arn:aws:iam::<ACCOUNT_ID>:role/ecsTaskExecutionRole",
"networkMode": "awsvpc",
"containerDefinitions": [
{
"name": "my-app",
"image": "<ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/my-app:latest",
"cpu": 256,
"memory": 512,
"essential": true,
"portMappings": [{ "containerPort": 80 }]
}
],
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512"
}
Register:
aws ecs register-task-definition --cli-input-json file://ecs-task-def.json
The execution role lets the task pull from ECR and send logs. The image is a placeholder; GitHub Actions will later push and update it.
2.4 Networking (Default VPC)
# Get default VPC
aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" --query "Vpcs[0].VpcId" --output text
# Get subnets in that VPC
aws ec2 describe-subnets --filters "Name=vpc-id,Values=<VPC_ID>" --query "Subnets[*].SubnetId" --output text
# Get default SG
aws ec2 describe-security-groups --filters "Name=vpc-id,Values=<VPC_ID>" --query "SecurityGroups[?GroupName=='default'].GroupId" --output text
# Open HTTP if needed
aws ec2 authorize-security-group-ingress --group-id <SG_ID> --protocol tcp --port 80 --cidr 0.0.0.0/0
2.5 Create Service
aws ecs create-service \
--cluster my-app-cluster \
--service-name my-app-service \
--task-definition my-app-task \
--desired-count 1 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-aaa,subnet-bbb],securityGroups=[sg-xxx],assignPublicIp=ENABLED}"
Service keeps 1 running copy, updates when a new task definition revision arrives.
Step 3 : Configure OpenID Connect (OIDC)
Why: avoid putting permanent AWS keys in GitHub.
3.1 Add GitHub as OIDC Identity Provider
In IAM → Identity providers → Add provider
Type: OpenID Connect
Audience: sts.amazonaws.com
Attach a trust policy like:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:your-username/ecsapp:ref:refs/heads/main"
},
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
}
]
}
Attach only what’s needed (I am giving full access but you can give only the required access):
- AmazonECS_FullAccess , AmazonEC2ContainerRegistryFullAccess
3.3 How OIDC Works at Runtime
GitHub runner asks token.actions.githubusercontent.com for a JWT.
JWT includes:
iss=https://token.actions...
sub=repo:you/ecsapp:ref:refs/heads/main
aud=sts.amazonaws.comAction configure-aws-credentials calls AssumeRoleWithWebIdentity with JWT.
AWS STS validates signature + claims + trust policy.
STS returns temporary keys (valid ~1h).
These keys are used by the workflow for ECR/ECS.
Step 4 : GitHub Actions Workflow
Create .github/workflows/deployment.yaml:
name: Deploy my-app to Amazon ECS
on:
push:
branches: [ master ]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::943653636298:role/GitHub_Actions_Role
aws-region: us-east-1
- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push Docker image
run: |
IMAGE_URI=\({{ steps.ecr.outputs.registry }}/my-app:\){{ github.sha }}
docker build -t $IMAGE_URI .
docker push $IMAGE_URI
- name: Render Amazon ECS task definition
id: taskdef
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ecs-task-def.json
container-name: my-app
image: \({{ steps.ecr.outputs.registry }}/my-app:\){{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.taskdef.outputs.task-definition }}
service: my-app-service
cluster: my-app-cluster
wait-for-service-stability: true
Push to GitHub:
git add .
git commit -m "add CI/CD"
git push origin master
Step 5 : First Deployment Flow
Push → triggers workflow.
GitHub gets OIDC token → AWS STS returns temporary creds.
Docker image builds and is pushed to ECR.
ECS task definition updated with new image tag.
ECS service does a rolling deployment → new container goes live.
Check ECS console → Cluster → Service → Task → copy Public IP → open in browser.

Conclusion
With this setup, every push to your GitHub repository triggers a secure, automated deployment to Amazon ECS. Using OpenID Connect, your workflow gets short-lived AWS credentials on demand, eliminating static keys and reducing risk. ECS Fargate runs your containers without managing servers, while ECR stores immutable images for each build.This approach is simple, scalable, and secure a modern DevOps pattern that combines fast CI/CD with least-privilege, keyless authentication.





