Docker
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.txtBEFORE 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
dbto 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: alwaysto 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.


