🚀 Dockerizing a Python Flask Service with Hot Reload (2025+)
This guide covers:
- Why Flask is the best choice for hot reload in Docker (and why Bottle falls short)
- How to structure your Dockerfile for multi-stage (dev/prod) builds
- How to write a dead-simple Flask app that hot reloads in Docker, using only modern Compose syntax (no version:!)
- The best way to configure service/container names for rock-solid interop and scripting
- How to avoid unnecessary image: and .env complications for local dev
Why Use Flask? Why Not Bottle?
Both Flask and Bottle are great for tiny APIs.
But when it comes to hot reloading inside Docker containers:
- Flask:
- Officially supports hot reload (flask run --reload)
- Detects code changes even when volume-mounted from the host
- Robust dev error reporting (tracebacks, pin codes, etc.)
- Works seamlessly with Docker's file watching
- Bottle:
- Has a built-in reloader, but it's unreliable in Dockerized environments (especially on Alpine)
- Struggles with subprocess forking, PID1, and sometimes just won't reload after changes
- If you want a frustration-free DX (developer experience), use Flask for your microservices
Project Directory Structure
Always separate your service code into subfolders by technology.
This lets you add more services (like Go, Node, or another Python app) later—without chaos or path collisions.
project-root/
├── docker-compose.dev.yml
└── python/
├── Dockerfile.python
├── main.py
└── requirements.txt
- docker-compose.dev.yml: Your Compose file (in the project root only)
- python/: The Flask service's source folder
- Dockerfile.python: Multi-stage Dockerfile for Flask
- main.py: Your Flask application entry point
- requirements.txt: Python dependencies
This structure: Keeps your Docker context clean, enables easy multi-service setups, and makes volume-mounting straightforward for hot reload.
1. Python requirements.txt` File
Keep it lean!
For hot-reload Flask, you only need this in python/requirements.txt:
flask
(Add other dependencies as needed.)
2. main.py (Minimal Flask App, Hot Reload Ready)
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route('/health')
def health():
return jsonify(status="kewl")
if __name__ == "__main__":
debug = os.getenv("FLASK_ENV", "production") == "development"
app.run(host='0.0.0.0', port=8080, debug=debug)
3. Dockerfile.python (Multi-stage: dev & prod)
FROM python:3.14.0rc1-alpine3.21 AS base
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# --- DEV STAGE ---
FROM base AS dev
ENV FLASK_ENV=development
ENV FLASK_APP=main.py
CMD ["flask", "run", "--host=0.0.0.0", "--port=8080", "--reload"]
# --- PROD STAGE ---
FROM base AS prod
ENV FLASK_ENV=production
ENV FLASK_APP=main.py
CMD ["flask", "run", "--host=0.0.0.0", "--port=8080"]
4. docker-compose.dev.yml (Modern Compose, No version:)
services:
my_app_py:
container_name: my_app_py
build:
context: ./python
dockerfile: Dockerfile.python
target: dev
expose:
- "8080"
ports:
- "9092:8080"
environment:
- FLASK_ENV=development
- FLASK_APP=main.py
volumes:
- ./python:/app
networks:
my_app_net:
ipv4_address: 172.29.0.5
networks:
my_app_net:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.29.0.0/16
Why this layout?
All code lives in python/, keeping things totally isolated. You can add a go/, node/, or postgres/ sibling directory for other services—no clutter, no naming collisions.
5. Run and Hot Reload
docker compose -f docker-compose.dev.yml up --build
Edit any .py file in python/—Flask will auto-restart the service inside Docker!
Test from host:
curl http://localhost:9092/health
# Should return: {"status": "kewl"}
6. Compose version: Is Deprecated
- 2025+ best practice: Omit the version: key in all Compose files.
- Docker Compose detects the version automatically; all features work fine.
7. Best Practices, TL;DR
- Keep each service's code in its own subfolder (python/, go/, etc.)
- Omit image: unless you want to push to a registry; build: is all you need for local/dev work.
- Use container_name: to ensure a stable, predictable name for scripts and Docker commands.
- Map ports for dev, use only expose: for internal-only prod services.
- Leverage Flask for dev hot reload—it Just Works in Docker, no hacks.
- Volume-mount your code in dev to enable true hot reload (volumes:).
- Add dependencies to python/requirements.txt only.
8. Summary Table
Service Name | Container Name | Build Context | Dockerfile | Dev Port | Internal Port | Hot Reload? | Notes |
---|---|---|---|---|---|---|---|
my_app_py | my_app_py | ./python | Dockerfile.python | 9092 | 8080 | ✅ | Flask, no image needed |
Just keep all the files related to Python in that dir, and you have a totally isolated and encapsulated Docker web app.
Conclusion
\\Dockerizing your Flask Python app is best-practice, and simple—\\just keep everything in its own folder, leverage multi-stage Docker builds, and enjoy perfect hot reload in dev.
By keeping your code for each service isolated (python/, go/, etc.), your project is ready to scale—no path conflicts, no messy root directories, and you can always add more services in the future.
Whether you're deploying PyFlask, the classic Flask framework, or anything in between, these patterns will serve you well.