Cloud Native 11 min read

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.

Subtle Storm
Subtle Storm
Subtle Storm
How to Deploy a Flask App with Docker: A Step‑by‑Step Guide

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 created

Sample 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.0

Gunicorn 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 images

Typical output:

REPOSITORY   TAG    IMAGE ID       CREATED          SIZE
flask-app    v1.0   abc123def456   10 seconds ago   180MB

Build 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.0

Flags 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 ps

Advanced 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.0

Pass 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.0

Limit resources:

docker run -d \
  --name flask-container \
  -p 8080:5000 \
  --memory="512m" \
  --cpus="1.0" \
  flask-app:v1.0

Performance Tuning

Alpine Base Image

FROM python:3.11-alpine
RUN apk add --no-cache gcc musl-dev linux-headers

Switch 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-network

This 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

cloud nativeDockerPythoncontainerizationFlaskDockerfileGunicorn
Subtle Storm
Written by

Subtle Storm

The micro era's marvels are boundlessly subtle.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.