The five things a production Dockerfile gets right
- Multi-stage — small final image.
- Non-root user — drop privileges before CMD.
- HEALTHCHECK — Docker (and orchestrators) can tell when your app is actually ready.
- Exec-form CMD — JSON array, not a shell string. So PID 1 receives signals correctly.
- LABELs — version, source, maintainer. Searchable in registries.
About signals and graceful shutdown
If you write CMD python app.py (shell form), Docker actually runs /bin/sh -c "python app.py". PID 1 is the shell, not Python. SIGTERM goes to the shell, which doesn't forward it. Your Python app gets killed by SIGKILL after the 10-second grace period — no chance for cleanup.
Always write CMD ["python", "app.py"] (exec form). PID 1 is Python directly. SIGTERM reaches your handler. Graceful shutdown works.