DEV Community

Mikhail Dorokhovich
Mikhail Dorokhovich

Posted on

dev.to + Reddit r/docker (Dockerfile best-practices and hardening lists are highly shareable)

A production Dockerfile for FastAPI: the two mistakes beginners always make

Chapter 6 of a local-Kubernetes series. Not another "your first Dockerfile" — it leads with the two things beginners get wrong that have real consequences: running as root (a security liability) and cache-busting layer order (a productivity tax).

Key takeaways

  • Layer order is caching. Put what rarely changes first. COPY requirements.txt + pip install before COPY ./app, so a one-line code edit doesn't reinstall every dependency. The reverse (COPY . . before install) is the most common anti-pattern.
  • Run as a non-root user. adduser --disabled-password --uid 10001 appuser then USER appuser; reinforce with securityContext.runAsNonRoot: true in the Pod. Least privilege, smaller attack surface.
  • Multi-stage build: install deps into a venv in a build stage, COPY --from=build /opt/venv /opt/venv into a clean runtime image — no compilers or pip caches in the final layer.
  • Base image trade-offs: slim (glibc, best wheel compatibility — the sane default), alpine (musl → pip often compiles from source, slow/fragile, DNS quirks), distroless (no shell/package manager, great secure runtime, exec-form only, pair with multi-stage).
  • exec form of CMD is not optional: ["fastapi","run",...] makes the app PID 1 and receives SIGTERM directly. Shell form wraps it in /bin/sh, SIGTERM never reaches the app, and graceful shutdown breaks.
  • FastAPI specifics: fastapi run (prod-tuned uvicorn) not bare uvicorn; never --reload in the cluster image; PYTHONUNBUFFERED=1 or logs never reach kubectl logs; add --proxy-headers behind Ingress.
  • HEALTHCHECK without curl: slim/distroless have no curl, so probe with Python's urllib.
  • The finale (and the trap): docker build does NOT make the image visible to k3d — its nodes run isolated containerd. Deliver it via k3d image import myapp:dev -c dev (+ imagePullPolicy: IfNotPresent) or push to the built-in registry.

Full article: https://dorokhovich.com/blog/local-k8s-containerizing-your-service?utm_source=devto&utm_medium=syndication&utm_campaign=local-k8s-containerizing-your-service

Top comments (0)