Prerequisites
- Basic understanding of programming concepts π
- Python installation (3.8+) π
- VS Code or preferred IDE π»
What you'll learn
- Understand the concept fundamentals π―
- Apply the concept in real projects ποΈ
- Debug common issues π
- Write clean, Pythonic code β¨
π― Introduction
Welcome to this exciting tutorial on Dockerfile and building images! π In this guide, weβll explore how to containerize your Python applications and create portable, reproducible environments.
Youβll discover how Docker can transform your deployment experience. Whether youβre building web applications π, microservices π₯οΈ, or data science projects π, understanding Docker is essential for modern Python development.
By the end of this tutorial, youβll feel confident creating your own Docker images for Python projects! Letβs dive in! πββοΈ
π Understanding Dockerfile
π€ What is a Dockerfile?
A Dockerfile is like a recipe for your application π¨. Think of it as step-by-step instructions that tell Docker how to build your application environment - just like following a cooking recipe to create your favorite dish! π³
In technical terms, a Dockerfile is a text file containing commands that Docker uses to build images. This means you can:
- β¨ Package your Python app with all dependencies
- π Create consistent environments across different machines
- π‘οΈ Isolate your application from system conflicts
π‘ Why Use Docker for Python?
Hereβs why Python developers love Docker:
- Environment Consistency π: βWorks on my machineβ becomes βworks everywhereβ
- Dependency Management π»: No more virtual environment headaches
- Easy Deployment π: Ship your app as a single container
- Scalability π§: Run multiple instances effortlessly
Real-world example: Imagine deploying a machine learning model π€. With Docker, you can package your model, Python version, and all libraries into one container that runs identically on your laptop, CI/CD pipeline, and production servers!
π§ Basic Syntax and Usage
π Simple Python Dockerfile
Letβs start with a friendly example:
# π Hello, Docker! Start with a Python base image
FROM python:3.11-slim
# π¨ Set the working directory in the container
WORKDIR /app
# π¦ Copy requirements first (Docker layer caching!)
COPY requirements.txt .
# π Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# π Copy your application code
COPY . .
# π― Specify the command to run your app
CMD ["python", "app.py"]
π‘ Explanation: Notice how we copy requirements.txt before the app code? This leverages Dockerβs layer caching - if your code changes but dependencies donβt, Docker reuses the cached layer!
π― Essential Dockerfile Instructions
Here are the commands youβll use daily:
# ποΈ FROM: Choose your base image
FROM python:3.11-slim # Slim = smaller size!
# π¨ WORKDIR: Set working directory
WORKDIR /app # All paths are relative to this
# π COPY: Copy files from host to container
COPY src/ ./src/ # Copy entire directories
COPY *.py ./ # Copy with patterns
# π RUN: Execute commands during build
RUN apt-get update && apt-get install -y gcc # System packages
RUN pip install numpy pandas # Python packages
# π ENV: Set environment variables
ENV PYTHONUNBUFFERED=1 # See Python output in real-time!
ENV APP_ENV=production
# πͺ EXPOSE: Document which ports your app uses
EXPOSE 8000 # Just documentation, doesn't open ports
# π¬ CMD: Default command when container starts
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0"]
π‘ Practical Examples
π Example 1: Flask Web Application
Letβs containerize a Flask app:
# app.py - Our Flask application π
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route('/')
def hello():
return jsonify({
'message': 'Hello from Docker! π³',
'environment': os.getenv('APP_ENV', 'development')
})
@app.route('/health')
def health():
return jsonify({'status': 'healthy β
'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
# Dockerfile for our Flask app π
FROM python:3.11-slim
# π Create app directory
WORKDIR /app
# π Copy and install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# π― Copy application
COPY app.py .
# π Set environment
ENV FLASK_APP=app.py
ENV APP_ENV=production
# πͺ Expose port
EXPOSE 5000
# π¬ Run the application
CMD ["python", "app.py"]
# requirements.txt π¦
flask==2.3.0
gunicorn==20.1.0
π― Build and run:
# ποΈ Build the image
docker build -t my-flask-app .
# π Run the container
docker run -p 5000:5000 my-flask-app
# π Visit http://localhost:5000
π€ Example 2: Data Science Project
Letβs containerize a machine learning project:
# predict.py - ML prediction service π
import pandas as pd
import joblib
from fastapi import FastAPI
from pydantic import BaseModel
# π― Load our trained model
model = joblib.load('model.pkl')
app = FastAPI()
class PredictionInput(BaseModel):
feature1: float
feature2: float
feature3: float
@app.post('/predict')
def predict(data: PredictionInput):
# π Create DataFrame for prediction
df = pd.DataFrame([data.dict()])
# π€ Make prediction
prediction = model.predict(df)[0]
return {
'prediction': float(prediction),
'emoji': 'π' if prediction > 0 else 'π'
}
@app.get('/')
def root():
return {'message': 'ML Model API π€', 'status': 'ready'}
# Dockerfile for ML project π€
FROM python:3.11-slim
# π οΈ Install system dependencies for scientific computing
RUN apt-get update && apt-get install -y \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# π¦ Copy and install Python dependencies
COPY requirements-ml.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# π Copy model and code
COPY model.pkl .
COPY predict.py .
# πͺ Expose API port
EXPOSE 8000
# π Run with uvicorn
CMD ["uvicorn", "predict:app", "--host", "0.0.0.0", "--port", "8000"]
π¦ Example 3: Multi-stage Build for Smaller Images
Advanced technique for optimization:
# ποΈ Stage 1: Builder stage
FROM python:3.11 AS builder
WORKDIR /app
# π¦ Install build dependencies
RUN apt-get update && apt-get install -y \
build-essential \
python3-dev
# π§ Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# π Install Python packages
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# π― Stage 2: Runtime stage (smaller!)
FROM python:3.11-slim
WORKDIR /app
# π Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# π¨ Copy application code
COPY src/ ./src/
COPY main.py .
# π Run the application
CMD ["python", "main.py"]
π Advanced Concepts
π§ββοΈ Docker Compose for Multi-container Apps
When your app needs multiple services:
# docker-compose.yml πͺ
version: '3.8'
services:
# π Web application
web:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/myapp
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
# πΎ PostgreSQL database
db:
image: postgres:15
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
# β‘ Redis cache
cache:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:
ποΈ Best Practices for Python Images
Advanced Dockerfile with all best practices:
# π― Use specific Python version
FROM python:3.11.4-slim-bullseye AS base
# π‘οΈ Security: Run as non-root user
RUN useradd -m -u 1000 appuser
# π Python environment settings
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# ποΈ Builder stage
FROM base AS builder
# π¦ Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# π§ Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# π Install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# π― Runtime stage
FROM base AS runtime
# π Copy virtual environment
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# π€ Switch to non-root user
USER appuser
WORKDIR /home/appuser/app
# π¨ Copy application with correct ownership
COPY --chown=appuser:appuser . .
# π Run application
CMD ["python", "-m", "myapp"]
β οΈ Common Pitfalls and Solutions
π± Pitfall 1: Huge Image Sizes
# β Wrong way - installs unnecessary packages!
FROM python:3.11
RUN apt-get update && apt-get install -y \
python3-dev \
build-essential \
libpq-dev
COPY . .
RUN pip install -r requirements.txt
# π₯ Results in 1GB+ image!
# β
Correct way - use slim base and multi-stage!
FROM python:3.11-slim AS builder
# Install build deps only in builder stage
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
&& rm -rf /var/lib/apt/lists/*
# ... builder stage ...
FROM python:3.11-slim
# Copy only what's needed from builder
# β¨ Results in <200MB image!
π€― Pitfall 2: Rebuilding Everything on Code Changes
# β Dangerous - rebuilds dependencies every time!
FROM python:3.11-slim
WORKDIR /app
COPY . . # π₯ Copies everything first!
RUN pip install -r requirements.txt # Reinstalls on any change!
# β
Safe - leverages layer caching!
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt . # π¦ Dependencies first
RUN pip install -r requirements.txt # Cached if unchanged!
COPY . . # π― Code last
π Pitfall 3: Missing Python Output
# β No output visible during container run
FROM python:3.11-slim
COPY . .
CMD ["python", "app.py"] # π₯ Output is buffered!
# β
See output immediately!
FROM python:3.11-slim
ENV PYTHONUNBUFFERED=1 # π― Disable buffering
COPY . .
CMD ["python", "app.py"] # β¨ Real-time output!
π οΈ Best Practices
- π― Use Specific Tags:
python:3.11.4-slim
notpython:latest
- π¦ Optimize Layers: Group related commands, clean up in same layer
- π‘οΈ Security First: Run as non-root user, scan for vulnerabilities
- π¨ Use .dockerignore: Donβt copy unnecessary files
- β¨ Multi-stage Builds: Separate build and runtime dependencies
Example .dockerignore
:
# π« Don't copy these to Docker image
__pycache__
*.pyc
.git
.pytest_cache
.env
.venv
env/
venv/
.DS_Store
.coverage
htmlcov/
.idea/
.vscode/
π§ͺ Hands-On Exercise
π― Challenge: Build a Containerized API with Database
Create a Docker setup for a Python API that:
π Requirements:
- β FastAPI application with health check endpoint
- ποΈ PostgreSQL database connection
- π Database migrations with Alembic
- π Background task queue with Celery
- π¨ Multi-stage Dockerfile for optimal size
- π³ Docker Compose for local development
π Bonus Points:
- Add Redis for caching
- Include development vs production configurations
- Implement graceful shutdown handling
π‘ Solution
π Click to see solution
# main.py - FastAPI application π
from fastapi import FastAPI, BackgroundTasks
from sqlalchemy import create_engine
from celery import Celery
import os
import asyncpg
app = FastAPI(title="Containerized API π³")
# ποΈ Database connection
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://user:pass@localhost/db")
# π Celery setup
celery_app = Celery(
"tasks",
broker=os.getenv("REDIS_URL", "redis://localhost:6379")
)
@app.get("/")
async def root():
return {
"message": "Welcome to our Dockerized API! π",
"status": "running",
"environment": os.getenv("APP_ENV", "development")
}
@app.get("/health")
async def health_check():
# π₯ Check database connection
try:
conn = await asyncpg.connect(DATABASE_URL)
await conn.fetchval("SELECT 1")
await conn.close()
db_status = "healthy β
"
except Exception as e:
db_status = f"unhealthy β: {str(e)}"
return {
"api": "healthy β
",
"database": db_status,
"cache": "healthy β
" # Add real Redis check
}
@app.post("/tasks")
async def create_task(background_tasks: BackgroundTasks):
# π Queue background task
task = celery_app.send_task("process_data", args=["data"])
return {
"task_id": task.id,
"status": "queued π",
"message": "Task queued successfully!"
}
# Dockerfile - Optimized multi-stage build ποΈ
# Stage 1: Builder
FROM python:3.11-slim AS builder
# π οΈ Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
postgresql-dev \
python3-dev \
&& rm -rf /var/lib/apt/lists/*
# π¦ Create virtual environment
WORKDIR /app
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# π Install Python dependencies
COPY requirements.txt .
RUN pip install --upgrade pip && \
pip install -r requirements.txt
# Stage 2: Runtime
FROM python:3.11-slim AS runtime
# π‘οΈ Create non-root user
RUN useradd -m -u 1000 appuser
# π¦ Install runtime dependencies only
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
&& rm -rf /var/lib/apt/lists/*
# π Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# π Set up app directory
WORKDIR /home/appuser/app
COPY --chown=appuser:appuser . .
# π Environment setup
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
APP_ENV=production
# π€ Switch to non-root user
USER appuser
# πͺ Expose port
EXPOSE 8000
# π Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# docker-compose.yml - Local development setup πͺ
version: '3.8'
services:
# π API Service
api:
build:
context: .
target: runtime
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
- APP_ENV=development
depends_on:
- db
- redis
volumes:
- ./:/home/appuser/app # Hot reload in dev
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# πΎ PostgreSQL Database
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
# β‘ Redis Cache
redis:
image: redis:7-alpine
ports:
- "6379:6379"
command: redis-server --appendonly yes
# π Celery Worker
worker:
build:
context: .
target: runtime
command: celery -A tasks worker --loglevel=info
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
# π― Database Migrations
migrate:
build:
context: .
target: runtime
command: alembic upgrade head
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
depends_on:
- db
volumes:
postgres_data:
# Makefile - Convenient commands π οΈ
.PHONY: help build up down logs shell test
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
build: ## Build all containers
docker-compose build
up: ## Start all services
docker-compose up -d
@echo "π API running at http://localhost:8000"
down: ## Stop all services
docker-compose down
logs: ## View logs
docker-compose logs -f
shell: ## Open shell in API container
docker-compose exec api bash
test: ## Run tests
docker-compose exec api pytest
migrate: ## Run database migrations
docker-compose run --rm migrate
π Key Takeaways
Youβve learned so much! Hereβs what you can now do:
- β Create Dockerfiles for Python applications πͺ
- β Optimize image size with multi-stage builds π‘οΈ
- β Use Docker Compose for multi-service apps π―
- β Follow best practices for security and efficiency π
- β Build production-ready containerized Python apps! π
Remember: Docker makes deployment predictable and scalable. No more βworks on my machineβ problems! π€
π€ Next Steps
Congratulations! π Youβve mastered Docker for Python applications!
Hereβs what to do next:
- π» Practice with the exercises above
- ποΈ Containerize one of your existing Python projects
- π Explore Kubernetes for orchestrating containers
- π Learn about CI/CD pipelines with Docker
Remember: Every DevOps expert started by writing their first Dockerfile. Keep building, keep deploying, and most importantly, have fun! π
Happy containerizing! ππ³β¨