How to Deploy a Flask App with Docker: A Step‑by‑Step Guide
This guide walks you through containerizing a Flask web application with Docker, covering project setup, Dockerfile creation, image building, container runtime options, and performance optimizations such as multi‑stage builds, non‑root users, and Gunicorn configuration.
Why Containerize a Flask Application?
Containerization ensures environment consistency across development, testing, and production, enables rapid, second‑scale deployments, simplifies horizontal scaling, provides resource isolation, and treats images as versioned artifacts that can be rolled back instantly.
Project Structure
A typical Flask project looks like this:
flask-app/
├── app.py # main application
├── requirements.txt # dependencies
├── config.py # configuration
├── templates/
│ └── index.html
├── static/
│ ├── css/
│ └── js/
└── Dockerfile # will be createdSample Application Code (app.py)
from flask import Flask, render_template, jsonify
import os
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/health')
def health_check():
return jsonify({
'status': 'healthy',
'environment': os.getenv('FLASK_ENV', 'production')
})
@app.route('/api/info')
def info():
return jsonify({
'app': 'Flask Docker Demo',
'version': '1.0.0'
})
if __name__ == '__main__':
# Container must listen on 0.0.0.0, not 127.0.0.1
app.run(host='0.0.0.0', port=int(os.getenv('PORT', 5000)),
debug=os.getenv('FLASK_DEBUG', 'False') == 'True')The requirements.txt lists:
Flask==3.0.0
gunicorn==21.2.0
python-dotenv==1.0.0Gunicorn is recommended for production because the built‑in Flask server is not suitable for high‑traffic workloads.
Core: Writing the Dockerfile
Basic Dockerfile
# Step 1: Base image
FROM python:3.11-slim
# Step 2: Set work directory
WORKDIR /app
# Step 3: Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Step 4: Copy source code
COPY . .
# Step 5: Expose port
EXPOSE 5000
# Step 6: Startup command
CMD ["python", "app.py"]Each instruction is explained: the slim Python image keeps the image small; WORKDIR creates /app; copying requirements.txt first leverages Docker layer caching; --no-cache-dir reduces image size; EXPOSE documents the listening port.
Production‑Optimized Dockerfile
# Multi‑stage build (optional)
FROM python:3.11-slim as base
# Environment variables for safety and speed
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# Create non‑root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
# Install system build tools if needed
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc && rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code with proper ownership
COPY --chown=appuser:appuser . .
# Switch to non‑root user
USER appuser
EXPOSE 5000
# Use Gunicorn in production
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "--threads", "2", "--timeout", "60", "app:app"]Key optimizations include setting PYTHONUNBUFFERED for real‑time logs, disabling bytecode generation, using a non‑root user for security, and configuring Gunicorn with four workers (CPU cores × 2 + 1) and two threads per worker.
Building the Docker Image
Basic Build Command
docker build -t flask-app:v1.0 .The -t flag tags the image as flask-app:v1.0. The trailing dot tells Docker to use the current directory as the build context.
Inspecting the Result
docker imagesTypical output:
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-app v1.0 abc123def456 10 seconds ago 180MBBuild Optimizations
Use a .dockerignore file to exclude unnecessary files (e.g., __pycache__, .git, virtual‑env directories, logs, and test caches) and dramatically shrink the build context.
Running the Container
Basic Run Command
docker run -d \
--name flask-container \
-p 8080:5000 \
flask-app:v1.0Flags explained: -d runs in detached mode, --name assigns a friendly name, and -p maps host port 8080 to container port 5000.
Verification
curl http://localhost:8080/api/health
Open a browser at http://localhost:8080
Check logs with docker logs flask-container Inspect container status with
docker psAdvanced Runtime Options
Mount data volumes for persistence:
docker run -d \
--name flask-container \
-p 8080:5000 \
-v $(pwd)/data:/app/data \
-v $(pwd)/logs:/app/logs \
flask-app:v1.0Pass environment variables:
docker run -d \
--name flask-container \
-p 8080:5000 \
-e FLASK_ENV=production \
-e DATABASE_URL=postgresql://user:pass@db:5432/mydb \
--env-file .env \
flask-app:v1.0Limit resources:
docker run -d \
--name flask-container \
-p 8080:5000 \
--memory="512m" \
--cpus="1.0" \
flask-app:v1.0Performance Tuning
Alpine Base Image
FROM python:3.11-alpine
RUN apk add --no-cache gcc musl-dev linux-headersSwitch to the asynchronous Gevent worker for Gunicorn:
pip install gevent
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "--worker-class", "gevent", "app:app"]Reverse Proxy with Nginx
Add an Nginx service in docker‑compose.yml:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- web
networks:
- app-networkThis completes a zero‑to‑one containerized deployment of a Flask application, illustrating modern software‑engineering practices such as immutable infrastructure, non‑root containers, and cloud‑native tooling.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
