FastAPI Dockerfile for Production: Slim, Multi-Stage, Uvicorn
This FastAPI Dockerfile installs your dependencies in a throwaway builder stage and copies only the installed packages into a clean python:3.13-slim runtime. The app runs under Uvicorn as an unprivileged user on port 8000. It suits APIs deployed behind a load balancer or reverse proxy that need a small image without compilers or caches.
- CI status
- Build passing
- Image size (uncompressed)
- 136.2 MB
- Build time
- 13 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
ENV PIP_NO_CACHE_DIR=1 PIP_DISABLE_PIP_VERSION_CHECK=1
COPY requirements.txt .
RUN pip install --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 ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"].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 uvicorn in requirements.txt next to fastapi.
- Pin every dependency in requirements.txt so rebuilds are reproducible.
How this Dockerfile works
FROM python:3.13-slim AS builderStarts a named builder stage. Everything installed here, including pip's working files and any build tools, stays behind when the final image is assembled.
ENV PIP_NO_CACHE_DIR=1 PIP_DISABLE_PIP_VERSION_CHECK=1Stops pip from writing its download cache and from checking for a newer pip on every build, which keeps layers smaller and builds quieter.
COPY requirements.txt . RUN pip install --prefix=/install -r requirements.txtCopying only requirements.txt first means this expensive layer is reused until your dependencies change. Installing into /install keeps the packages in one directory that is easy to copy out.
COPY --from=builder /install /usr/localBrings the installed packages into the runtime stage at the location Python already searches, so no PYTHONPATH tweaks are needed.
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1Skips writing .pyc files into the container and flushes stdout immediately, so your logs show up in docker logs without delay.
RUN useradd --create-home --uid 10001 app USER appCreates a dedicated user with a fixed UID and switches to it. A compromised process then has no root privileges inside the container.
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"]Binds Uvicorn to all interfaces so the published port is reachable. --proxy-headers makes FastAPI trust X-Forwarded-* headers from the proxy in front of it.
Build and run
docker build -t fastapi-app .
docker run --rm -p 8000:8000 fastapi-appRun both commands from the project root, where the Dockerfile and .dockerignore live. Then open http://localhost:8000/docs to check that the interactive API docs load from inside the container.
Other FastAPI Dockerfile variants
This slim, single-process Uvicorn setup is the default for FastAPI. It is a good fit when your platform scales containers horizontally and you would rather run more small containers than more workers inside one. More FastAPI variants will appear in the table below once they pass CI.
| 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
- ModuleNotFoundError: No module named 'app'
- The CMD expects a package called app with main.py inside it. Either move your code to that layout or change app.main:app to match your module path.
- uvicorn: executable file not found in $PATH
- uvicorn is missing from requirements.txt, so the builder stage never installed it. Add it, rebuild, and confirm it is listed in your pinned dependencies.
- Redirects use http:// even though the site is served over HTTPS
- Your proxy must send X-Forwarded-Proto. --proxy-headers already trusts it, but some setups also need Uvicorn's forwarded-allow-ips option set to the proxy's address.
FAQ
- Why not use the full python image instead of slim?
- The full image ships compilers and development headers that your API never needs at runtime. Slim keeps the attack surface and pull time down. If a dependency needs to compile, it can do so in the builder stage.
- Should I run Gunicorn with Uvicorn workers?
- Inside containers you usually scale by running more replicas rather than more workers per container, so plain Uvicorn is simpler. A Gunicorn-managed variant makes sense on a single large host.
- How do I add a health check?
- Expose a lightweight route such as /health in FastAPI and point your orchestrator's HTTP probe at port 8000. That works without adding curl to the slim image.