Flask Docker Basics
Docker packages your Flask application with all its dependencies into a container — a self-contained unit that runs identically on every machine. The classic problem "it works on my computer but not the server" disappears when you use Docker.
The Shipping Container Analogy
Before standardized shipping containers, loading cargo onto different ships required custom handling for every shipment. A standardized container fits every ship, truck, and crane without modification. Docker containers work the same way — one container image runs on your laptop, a coworker's Mac, a Linux server, or a cloud platform without any changes.
Without Docker: With Docker: Dev: Python 3.11, Flask 3.0 Container: Python 3.11 + Flask 3.0 Server: Python 3.9, Flask 2.2 Runs same container on every machine Result: version conflicts Result: identical behavior everywhere
Core Docker Concepts
| Concept | Analogy | Description |
|---|---|---|
| Image | Blueprint | A read-only template with your app and dependencies |
| Container | Running building | A running instance of an image |
| Dockerfile | Construction plan | Instructions for building an image |
| Registry | App store | Storage for images (Docker Hub, AWS ECR) |
Installing Docker
Download Docker Desktop from docker.com for Windows and macOS. On Linux:
apt install docker.io docker-compose -y
systemctl start docker
systemctl enable dockerWriting a Dockerfile
Create a file named Dockerfile (no extension) in your project root:
# Use the official Python 3.11 image as the base
FROM python:3.11-slim
# Set the working directory inside the container
WORKDIR /app
# Copy dependency list first (Docker caches this layer)
COPY requirements.txt .
# Install all Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the application code
COPY . .
# Set environment variables
ENV FLASK_ENV=production
ENV PYTHONUNBUFFERED=1
# Expose the port Gunicorn will listen on
EXPOSE 8000
# Command to run when the container starts
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "run:app"]The .dockerignore File
Prevent unnecessary files from entering the image. Create .dockerignore:
venv/
__pycache__/
*.pyc
.env
.git/
tests/
*.mdThis keeps the image small and prevents secrets in .env from being baked into the image.
Building and Running the Image
# Build the image (tag it as 'myflaskapp:latest')
docker build -t myflaskapp:latest .
# Run a container from the image
docker run -d \
-p 8000:8000 \
-e SECRET_KEY=my-secret \
-e DATABASE_URL=sqlite:///app.db \
--name flaskapp \
myflaskapp:latest
# Check running containers
docker ps
# View container logs
docker logs flaskapp
# Stop the container
docker stop flaskappDocker Compose for Multi-Container Apps
Real Flask apps often use a separate database container. Docker Compose defines and starts multiple containers together with one command.
Create docker-compose.yml:
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
environment:
- SECRET_KEY=mysecret
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
depends_on:
- db
volumes:
- .:/app
db:
image: postgres:15
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:# Start all services
docker-compose up -d
# Run migrations inside the web container
docker-compose exec web flask db upgrade
# Stop all services
docker-compose downHow Docker Compose Networking Works
docker-compose creates a private network:
[web container] ──▶ [db container]
hostname: web hostname: db
port: 8000 port: 5432
Flask connects to PostgreSQL at:
postgresql://postgres:password@db:5432/myapp
▲
hostname 'db' resolves to the
db container's IP automatically
Publishing to Docker Hub
# Log in to Docker Hub
docker login
# Tag your image
docker tag myflaskapp:latest yourusername/myflaskapp:latest
# Push to Docker Hub
docker push yourusername/myflaskapp:latest
# On any other machine, run it with:
docker pull yourusername/myflaskapp:latest
docker run -d -p 8000:8000 yourusername/myflaskapp:latestDockerfile Layer Caching
Docker caches each step (layer) in the Dockerfile. If a layer has not changed, Docker reuses the cache. Copying requirements.txt before the rest of the code means Docker only reinstalls packages when requirements.txt changes — not on every code edit. This dramatically speeds up build times during development.
COPY requirements.txt . ← cached unless requirements change RUN pip install ... ← cached unless requirements change COPY . . ← rebuilds on every code change (fast, no pip)
Summary
Docker packages your Flask app and all its dependencies into a portable container. Write a Dockerfile with instructions to build the image, then run it with docker run. Use Docker Compose to manage multi-container setups like Flask + PostgreSQL with one command. Pass secrets through environment variables at runtime — never bake them into the image. Docker eliminates environment differences between development, staging, and production.
