Skip to main content

Command Palette

Search for a command to run...

Docker

Updated
•6 min read•View as Markdown

If you've ever heard "works on my machine" and wanted to flip a table — Docker is the fix. This blog is everything I learned about Docker + Python in one place. No fluff, just the stuff that matters.


🧠 What Even Is Docker?

Docker runs your app inside an isolated container — so it works the same on your laptop, your teammate's machine, and the production server. No more "but it works here!"

The Maggie analogy (because why not):

Docker Term Maggie Analogy
Dockerfile Recipe — step-by-step instructions
Image Ready Maggie packet — prepared, not cooked
Container Cooked Maggie — live, running right now

Problems Docker solves:

  • Python version mismatch between dev and server

  • "Works on my machine" nightmares

  • Setting up the same environment on 10 machines

  • Library conflicts between projects


📦 The 3 Core Concepts

Concept What It Is
Dockerfile Recipe — instructions to build an image
Image Snapshot of app + OS + dependencies (static)
Container Running instance of an image (live process)

🔧 Part 1 — Your First Dockerized Flask App

Project structure:

my-project/
├── app.py
├── requirements.txt
└── Dockerfile

app.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def home():
    return "Hello, Docker + Python 🚀"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

⚠️ host="0.0.0.0" is required. Without it, Flask only listens inside the container and your port mapping won't work.

requirements.txt

flask

Dockerfile

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

Line-by-line breakdown:

Instruction What it does
FROM python:3.9-slim Start from official Python image. slim = smaller size
WORKDIR /app Set working directory inside container
COPY requirements.txt . Copy requirements first (layer caching trick 👇)
RUN pip install ... Runs at BUILD time — installs libs into the image
COPY . . Copy all your code into the container
EXPOSE 5000 Documents the port (doesn't actually open it)
CMD [...] Runs when container starts. Only 1 CMD allowed

🧠 Layer caching trick: Copy requirements.txt BEFORE your code. Docker caches each layer. If you copy code first, every code change rebuilds the install layer = slow. Copy requirements first → installs cached → only code layer rebuilds = fast.

Build and run:

docker build -t python-app .
docker run -d -p 5000:5000 python-app

Open http://localhost:5000 — you're live inside Docker.

Port mapping: -p 8080:5000 means visit localhost:8080 on your machine → Docker forwards to port 5000 inside the container. Left = host. Right = container.


🐙 Part 2 — Docker Compose (Multiple Services)

Running web app + database separately is painful. Compose lets you define everything in one file and start it all with one command.

docker-compose.yml

version: '3.8'

services:

  web:
    build: .
    ports:
      - "5000:5000"
    environment:
      - DB_HOST=db
      - DB_PORT=5432
    depends_on:
      - db
    volumes:
      - .:/app

  db:
    image: postgres:15
    environment:
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=myapp
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Key concepts:

Key Meaning
build: . Build from Dockerfile in current directory
depends_on: Start db BEFORE web
volumes: .:/app Mount local code into container (hot reload!)
volumes: pgdata: Named volume — DB data persists across restarts
DB_HOST=db Services talk to each other using service name as hostname

🔗 All Compose services share the same network automatically. Docker DNS resolves db to the container's IP — no hardcoded IPs needed.

Commands:

docker compose up -d          # Start all services in background
docker compose up --build     # Rebuild images
docker compose down           # Stop everything
docker compose logs -f web    # Tail logs for web service
docker compose ps             # List running services

🚀 Part 3 — Production Setup (Gunicorn + Nginx)

Flask's dev server is single-threaded and not built for real traffic. Here's the production stack.

Gunicorn = Python WSGI server. Runs multiple worker processes simultaneously.

gunicorn --workers 4 --bind 0.0.0.0:5000 app:app
# Rule: workers = 2 × CPU cores + 1

Nginx = reverse proxy. Sits in front of Gunicorn — handles SSL, static files, rate limiting, load balancing.

Updated Dockerfile:

FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "--workers", "4", "--bind", "0.0.0.0:5000", "app:app"]

nginx/nginx.conf:

events {}

http {
    upstream flask_app {
        server web:5000;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://flask_app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}

Production docker-compose.yml:

version: '3.8'

services:

  web:
    build: .
    expose:
      - "5000"
    environment:
      - DB_HOST=db
    restart: always

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - web
    restart: always

  db:
    image: postgres:15
    environment:
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: always

volumes:
  pgdata:

Request flow: User → Nginx (port 80) → Gunicorn (port 5000) → Flask → PostgreSQL


☁️ Part 4 — Deploy on AWS EC2

Step 1 — Launch EC2

  • Ubuntu 22.04 LTS

  • t2.micro (free tier)

  • Security group: open port 22, 80, 443

Step 2 — SSH in

chmod 400 your-key.pem
ssh -i your-key.pem ubuntu@YOUR_EC2_IP

Step 3 — Install Docker

sudo apt update && sudo apt upgrade -y
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker ubuntu
sudo apt install docker-compose-plugin -y
exit
# SSH back in, then verify:
docker --version

Step 4 — Upload and Run

git clone https://github.com/youruser/your-repo.git
cd your-repo
docker compose up -d --build
docker compose ps

Your app is live at http://YOUR_EC2_IP

💡 Add restart: always to each service — containers auto-start on server reboot.


⚡ Full Flow Summary

Stage What You Do
1. App code app.py with host=0.0.0.0
2. Dependencies requirements.txt
3. Build instructions Dockerfile
4. Build image docker build -t myapp .
5. Run container docker run -d -p 5000:5000 myapp
6. Multi-service docker-compose.yml
7. Production Gunicorn + Nginx
8. Deploy EC2 + SSH + git clone + compose up

That's Docker + Python from scratch to production. Once the mental model clicks — Dockerfile is a recipe, image is the packet, container is the cooked thing — everything else follows naturally.

Drop a comment if something's unclear. Happy to go deeper on any part.