FastAPI Dockerfile with Gunicorn and Uvicorn Workers
This FastAPI Dockerfile runs Gunicorn as a process manager with Uvicorn workers on python:3.13-slim. Dependencies are installed in a builder stage, and the app runs as a non-root user on port 8000. Use it when one container should serve requests with several worker processes, for example on a single VM or a platform that bills per container.
- CI status
- Build passing
- Image size (uncompressed)
- 136.2 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.main:app", "--worker-class", "uvicorn_worker.UvicornWorker", "--bind", "0.0.0.0:8000", "--workers", "2"].git
__pycache__/
*.pyc
.venv/
.env
.pytest_cache/
Dockerfile
.dockerignoreBefore you build
- Make your FastAPI instance importable as app.main:app, or change the module path in the CMD line.
- List gunicorn and uvicorn-worker in requirements.txt next to fastapi.
- Pin all dependencies in requirements.txt for reproducible builds.
How this Dockerfile works
FROM python:3.13-slim AS builderA throwaway stage that installs packages. Pip's leftovers and any build tooling stay behind when the runtime image is assembled.
RUN pip install --no-cache-dir --prefix=/install -r requirements.txtInstalls everything into one prefix without pip's cache, so a single directory can be copied into the final stage.
COPY --from=builder /install /usr/localPuts the installed packages and the gunicorn executable where the system Python and PATH already look.
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1Skips .pyc files and flushes output immediately, so worker logs appear in docker logs without buffering.
RUN useradd --create-home --uid 10001 app USER appRuns Gunicorn and every worker as an unprivileged user with a fixed UID.
CMD ["gunicorn", "app.main:app", "--worker-class", "uvicorn_worker.UvicornWorker", "--bind", "0.0.0.0:8000", "--workers", "2"]Gunicorn supervises two worker processes and restarts any that crash. The uvicorn-worker class runs FastAPI's async event loop inside each one.
Build and run
docker build -t fastapi-app .
docker run --rm -p 8000:8000 fastapi-appBuild from the project root, then open http://localhost:8000/docs. The startup log shows one line per booted worker, which confirms Gunicorn spawned them.
Other FastAPI Dockerfile variants
Plain Uvicorn with one process is simpler and fits orchestrators that scale by adding containers. Gunicorn with Uvicorn workers fits setups where each container should use several CPU cores. Both are listed in the table below with their measured sizes.
| Variant | Runtime image | Image size | CI |
|---|---|---|---|
| Gunicorn + Uvicorn workers | python:3.13-slim | 136.2 MB | Passing |
| Python slim + Uvicorn (multi-stage) | python:3.13-slim | 136.2 MB | Passing |
Troubleshooting
- Error: class uri 'uvicorn_worker.UvicornWorker' invalid or not found
- The uvicorn-worker package is missing. Add it to requirements.txt. The older uvicorn.workers module is deprecated in recent Uvicorn releases.
- Workers are killed with WORKER TIMEOUT during slow requests
- Gunicorn's default timeout is shorter than your slowest request. Raise it with --timeout, or move long-running jobs to a background queue.
- Memory use grows with every worker you add
- Each worker is a separate Python process with its own copy of the app. Size --workers to the container's memory limit, not only to its CPU count.
FAQ
- How many workers should I configure?
- Start with one or two per CPU core given to the container, then load-test. Async FastAPI apps often need fewer workers than synchronous frameworks for the same traffic.
- Why not just run more Uvicorn containers?
- On Kubernetes that is often better. On a single host or a per-container billing platform, several workers in one container use the available cores more cheaply.
- Does Gunicorn work with FastAPI's lifespan events?
- Yes. Each Uvicorn worker runs the app's startup and shutdown events independently, so connection pools are created once per worker process.