Back to Slides
Makers  /  Cloud Engineering

Containerising a Flask App
and Deploying to AWS Fargate

From local Python app to cloud-hosted container

Python Docker Amazon ECR Amazon ECS AWS Fargate eu-west-2

johnnygeambasu.com

The App

What it does

  • Simple Flask Hello World
  • Renders an HTML template at /
  • Runs on port 3030
  • Pure Python, no database, no auth
Why start here? A minimal app keeps focus on the deployment pipeline, not the application logic. The same steps apply to any Flask app.
from flask import Flask, render_template
import os

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

if __name__ == "__main__":
    port = int(
        os.environ.get('PORT', 3030)
    )
    app.run(
        debug=True,
        host='0.0.0.0',
        port=port
    )

Why Containers?

The problem

  • "Works on my machine"
  • Different Python versions across environments
  • Missing dependencies on the server
  • Bugs that can't be replicated reliably

The solution

  • Package the app and its environment together
  • Runs identically on any machine with Docker
  • Isolated from the host OS
  • Reproducible builds every time
Why it matters: Containers are the industry standard for shipping software. ECR, ECS, Fargate, and Kubernetes are all built around them.

The Dockerfile

Building for AWS

On an Apple Silicon Mac (M1/M2/M3), your machine runs arm64. AWS runs linux/amd64. If you don't specify the platform at build time, the image silently fails on AWS.

# Wrong — builds for arm64 (your Mac)
docker build -t contain-my-flask .

# Correct — builds for linux/amd64 (AWS)
docker buildx build \
  --platform linux/amd64 \
  -t contain-my-flask:1.0.0 .
Common gotcha: The container runs fine locally but fails to start on Fargate. This is almost always the platform mismatch.
FROM python:3.8-alpine

COPY . /app

WORKDIR /app

RUN pip install -r requirements.txt

CMD ["python", "/app/main.py"]
Why Alpine? ~50MB vs 900MB+ for a standard Python image. Smaller image, faster ECR pulls, smaller attack surface.

Amazon ECR

Elastic Container Registry — your private image store

What you did

  • Created a private ECR repository
  • Authenticated Docker with AWS CLI
  • Tagged the image with the ECR URI
  • Pushed the image to the registry
Why ECR over Docker Hub? Private by default, integrated with AWS IAM, and co-located with your ECS cluster in eu-west-2.
# Authenticate
aws ecr get-login-password \
  --region eu-west-2 \
  | docker login \
    --username AWS \
    --password-stdin \
    <acct>.dkr.ecr.eu-west-2.amazonaws.com

# Tag
docker tag contain-my-flask:latest \
  <acct>.dkr.ecr.eu-west-2.amazonaws.com\
  /contain-my-flask:latest

# Push
docker push \
  <acct>.dkr.ecr.eu-west-2.amazonaws.com\
  /contain-my-flask:latest

Amazon ECS + Fargate

ECS (Elastic Container Service)

  • Orchestrates where and how containers run
  • Manages task scheduling and scaling
  • Monitors container health

Fargate (the compute layer)

  • Runs containers without EC2 instances
  • No servers to patch or manage
  • Pay per task, not per idle server
EC2 vs Fargate
Servers
You manage EC2
AWS manages it
Patching
Your responsibility
Handled by AWS
Scaling
Manual config
Per-task scaling
Billing
Per idle hour
Per task runtime
Focus
Infra + app
App only
Why Fargate? For a small app, EC2 adds overhead with no benefit.

Deploy and Run

Steps to go live

  • Create an ECS Cluster (logical grouping)
  • Create a Service from the Task Definition
  • Fargate pulls the image from ECR
  • Task starts. App is live on a public IP.
Region matters: ECR, ECS, and your VPC must all be in eu-west-2. Mixing regions means Fargate cannot pull the image.
$ aws ecs create-cluster \
  --cluster-name contain-my-flask

{ "cluster": { "status": "ACTIVE" } }

$ aws ecs create-service \
  --cluster contain-my-flask \
  --task-definition contain-my-flask \
  --launch-type FARGATE

{ "service": { "status": "ACTIVE" } }

$ curl http://<public-ip>:3030/
Hello World!

Architecture Overview

Flask App main.py
-->
Docker docker build
-->
ECR image registry
-->
Task Def ECS config
-->
Fargate serverless compute
-->
Public IP :3030
Build Ship Run
Build Flask source packaged into a Docker image using the Dockerfile
Ship Image pushed to a private ECR repository in eu-west-2
Run ECS pulls from ECR and Fargate runs the task on a public IP

What I Learned

01

Containers solve real problems. Packaging the app and its environment together eliminates "works on my machine". The image runs identically everywhere.

02

ECR, ECS, and Fargate form a pipeline. Each AWS service has one job: store the image, orchestrate the task, provide the compute. They compose cleanly.

03

Region consistency is non-negotiable. All AWS services must be in the same region. One mismatch breaks the whole deployment.

04

Fargate removes the undifferentiated heavy lifting. No EC2 instances, no patching, no capacity planning. Define the task and run it.

Thank you

Questions?

Flask  /  Docker  /  Amazon ECR  /  Amazon ECS  /  AWS Fargate

johnnygeambasu.com

← → navigate · Esc overview