Flask Dockerfile for Production with Gunicorn (Multi-Stage)
This Flask Dockerfile installs dependencies in a builder stage and serves the app with Gunicorn on python:3.13-slim as a non-root user. It listens on port 8000 with two worker processes, which suits small APIs and server-rendered sites behind a reverse proxy. Flask's built-in development server is never used in production.
- CI status
- Build passing
- Image size (uncompressed)
- 124.9 MB
- Build time
- 14 s
- Runtime image
python:3.13-slim- Port
- 8000
- Last built
- 2026-10-15
# syntax=docker/dockerfile:1
FROM python:3.13-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
RUN useradd --create-home --uid 10001 app
USER app
EXPOSE 8000
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--workers", "2"].git
__pycache__/
*.pyc
.venv/
.env
.pytest_cache/
Dockerfile
.dockerignoreBefore you build
- Expose your Flask instance as app inside app.py, or change app:app in the CMD line to match.
- List gunicorn in requirements.txt next to flask.
- Pin your dependencies in requirements.txt for reproducible builds.
How this Dockerfile works
FROM python:3.13-slim AS builderDependencies are installed in a separate stage, so pip's leftovers and any build tools never reach the runtime image.
COPY requirements.txt . RUN pip install --no-cache-dir --prefix=/install -r requirements.txtCopying only requirements.txt first caches this layer until dependencies change. Installing into /install makes the result easy to copy out.
COPY --from=builder /install /usr/localBrings Flask, Gunicorn and the rest into the runtime image at the location Python and the PATH already use.
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1Avoids writing .pyc files and flushes log output immediately, so request logs appear in docker logs right away.
RUN useradd --create-home --uid 10001 app USER appRuns Gunicorn and your code as an unprivileged user with a fixed UID instead of root.
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--workers", "2"]Gunicorn loads the app object from app.py, binds to all interfaces and supervises two worker processes, restarting any that crash.
Build and run
docker build -t flask-app .
docker run --rm -p 8000:8000 flask-appBuild from the project root, then open http://localhost:8000. Configuration such as SECRET_KEY or a database URL should be passed with -e flags when the container starts.
Other Flask Dockerfile variants
Gunicorn with synchronous workers is the standard way to serve Flask and fits most apps. Projects with long-polling or many slow outbound calls may prefer gevent workers or an ASGI server. Verified variants appear in the table below.
| Variant | Runtime image | Image size | CI |
|---|---|---|---|
| Python slim + Gunicorn | python:3.13-slim | 124.9 MB | Passing |
Troubleshooting
- Failed to find attribute 'app' in 'app'
- Gunicorn imported app.py but found no variable named app. Rename your Flask instance, or change app:app to module:variable to match your code.
- ModuleNotFoundError: No module named 'app'
- Your entry file has a different name or lives in a package. Point the CMD at the real module path, for example myproject.wsgi:app.
- Requests time out with WORKER TIMEOUT in the logs
- A request ran longer than Gunicorn's default timeout. Raise it with --timeout, or move slow work to a background job queue.
FAQ
- Why not use flask run in the container?
- Flask's built-in server is designed for development. It handles one request at a time by default and isn't hardened for production traffic, while Gunicorn manages multiple worker processes.
- How many Gunicorn workers should I use?
- The file starts with two. A common starting point is two to four per CPU core, adjusted after load testing and within the container's memory limit.
- Do I need Nginx in front of Gunicorn?
- In production a reverse proxy or load balancer usually handles TLS, slow clients and static files, while Gunicorn focuses on running your Python code.