Summary
The built image's CMD runs gunicorn only. Database migrations and collectstatic live exclusively in the compose service command. Any run of the image outside that one compose file boots gunicorn against an unmigrated database with no collected static — a 500 on first ORM query under DEBUG=False. Separately, the gunicorn worker/timeout tuning is hardcoded in the Dockerfile and re-declared in compose, so the two can silently drift.
Code
Dockerfile:33-35:
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", \
"--workers", "3", "--threads", "4", "--timeout", "300", \
"--max-requests", "500", "--max-requests-jitter", "50"]
docker-compose.yml web service command is where python manage.py migrate … && … collectstatic … && exec gunicorn … lives — so the image alone never migrates or collects static, and it re-declares the same gunicorn flags.
Concrete failure scenario
docker run <image>, a Kubernetes/Nomad Deployment, docker compose run web, or a future second compose target boots gunicorn against an unmigrated DB and with no collected static. Under DEBUG=False (the production settings path) the first request that touches the ORM 500s, and static assets are missing.
- The worker tuning (
--workers 3 --threads 4 --timeout 300 --max-requests 500) exists in two places with identical defaults; updating one and not the other ships stale tuning. The Dockerfile comment documents the streaming-export capacity model those numbers encode, so a drift here silently violates that model.
Why not a duplicate
#225 is that CI never builds or runs the image (so CMD/command/healthcheck regressions ship unverified). #218 is base-image/uv pinning for reproducibility. #202 is the (already-fixed-here) gunicorn 30s default timeout. Neither covers the CMD-vs-compose-command behavioral divergence or the image not being self-sufficient (no migrate/collectstatic in its own entrypoint).
Suggested fix
Give the image a self-sufficient entrypoint that runs migrate (and collectstatic where appropriate) before exec gunicorn, and single-source the gunicorn tuning (an entrypoint script or env-driven flags) so it isn't duplicated between Dockerfile and compose. At minimum, document that the image must not be run without the compose command.
Severity: LOW — operational/reproducibility gap; no data-integrity impact.
Summary
The built image's
CMDruns gunicorn only. Database migrations andcollectstaticlive exclusively in the compose servicecommand. Any run of the image outside that one compose file boots gunicorn against an unmigrated database with no collected static — a 500 on first ORM query underDEBUG=False. Separately, the gunicorn worker/timeout tuning is hardcoded in the Dockerfile and re-declared in compose, so the two can silently drift.Code
Dockerfile:33-35:docker-compose.ymlweb servicecommandis wherepython manage.py migrate … && … collectstatic … && exec gunicorn …lives — so the image alone never migrates or collects static, and it re-declares the same gunicorn flags.Concrete failure scenario
docker run <image>, a Kubernetes/Nomad Deployment,docker compose run web, or a future second compose target boots gunicorn against an unmigrated DB and with no collected static. UnderDEBUG=False(the production settings path) the first request that touches the ORM 500s, and static assets are missing.--workers 3 --threads 4 --timeout 300 --max-requests 500) exists in two places with identical defaults; updating one and not the other ships stale tuning. The Dockerfile comment documents the streaming-export capacity model those numbers encode, so a drift here silently violates that model.Why not a duplicate
#225 is that CI never builds or runs the image (so CMD/command/healthcheck regressions ship unverified). #218 is base-image/uv pinning for reproducibility. #202 is the (already-fixed-here) gunicorn 30s default timeout. Neither covers the CMD-vs-compose-command behavioral divergence or the image not being self-sufficient (no migrate/collectstatic in its own entrypoint).
Suggested fix
Give the image a self-sufficient entrypoint that runs
migrate(andcollectstaticwhere appropriate) beforeexec gunicorn, and single-source the gunicorn tuning (an entrypoint script or env-driven flags) so it isn't duplicated between Dockerfile and compose. At minimum, document that the image must not be run without the composecommand.Severity: LOW — operational/reproducibility gap; no data-integrity impact.