What does Django's manage.py check --deploy add, and how do you make it fail a CI build on deployment warnings?
answer
- registered with deploy=True
- mostly security.W-something
- which settings module it reads
- default fail level is ERROR
- --fail-level WARNING, silence the accepted
basics
~20 scheck --deploy adds checks registered with deploy=True, mostly security.* warnings such as W004, W018 and W020. They are warnings, so the default exit status stays 0; run it with production settings and --fail-level WARNING to fail CI.
solid answer
~40 s`--deploy` adds the checks registered with `@register(..., deploy=True)`: mostly the `security` family — `security.W004` (`SECURE_HSTS_SECONDS` unset), `W008` (`SECURE_SSL_REDIRECT` off), `W009` (weak `SECRET_KEY`), `W012`/`W016` (session and CSRF cookies not `Secure`), `W018` (`DEBUG` on), `W020` (empty `ALLOWED_HOSTS`) — plus, in Django 6.1, `mail.E001` for a development email backend in `MAILERS['default']`. Two traps make it useless in CI by default. It checks whichever settings module is loaded, so it must run with the production module (`--settings=` or `DJANGO_SETTINGS_MODULE`). And almost all of these are **warnings**, while `check` exits non-zero only at `ERROR` or above. Add `--fail-level WARNING`, and list any warning you have consciously accepted in `SILENCED_SYSTEM_CHECKS` in the production settings.
code
bash · 3 linespython manage.py check --deploy --fail-level WARNING \
--settings=shopsite.settings.production
echo "exit=$?" # 1 if any unsilenced warning or error was reportedgo deeper
Recall that check --deploy adds production-only checks, mainly security warnings such as DEBUG left on or an empty ALLOWED_HOSTS.
Explain deploy=True registration, why the settings module matters, the ERROR default fail level, and --fail-level WARNING for a CI gate.
Show how you wire it into the release pipeline with production settings and dummy secrets, and govern a short, justified SILENCED_SYSTEM_CHECKS list.
Decide which classes of misconfiguration must block a release organisation-wide, and back the automated check with a checklist for what settings cannot reveal.
## What --deploy adds A check can be registered as a **deployment check** with `@register(<tags>, deploy=True)`. Those checks are stored separately and only run when `manage.py check --deploy` is used; `runserver`, `migrate` and a plain `check` never run them. The reasoning is that they only make sense against **production settings** — in development, `DEBUG=True` and cookies without the `Secure` flag are normal. The deployment set is mostly Django's `security` checks, each with a stable id: | Id | Level | What it reports | |---|---|---| | `security.W004` | Warning | `SECURE_HSTS_SECONDS` is not set while `SecurityMiddleware` is enabled | | `security.W008` | Warning | `SECURE_SSL_REDIRECT` is not `True` | | `security.W009` | Warning | `SECRET_KEY` is short, has few unique characters, or starts with `django-insecure-` | | `security.W012` | Warning | `SESSION_COOKIE_SECURE` is not `True` | | `security.W016` | Warning | `CSRF_COOKIE_SECURE` is not `True` | | `security.W018` | Warning | `DEBUG` is `True` | | `security.W020` | Warning | `ALLOWED_HOSTS` is empty | | `mail.E001` | Error | The `'default'` entry of `MAILERS` uses a development-only backend (new in 6.1) | What each of those settings does, and how to choose its value, is a security topic; here the point is the mechanism that reports them. ## Trap 1: the settings module `check --deploy` inspects **whatever settings are loaded**. Run it with your development settings and it reports a wall of warnings that are all "correct" for development and teach nothing. The docs suggest two options: - Point it at the production module: `manage.py check --deploy --settings=shopsite.settings.production`, or export `DJANGO_SETTINGS_MODULE`. - Or run it **on** the production or staging environment, where the right module and environment variables are already in place. In CI the first option also means the build must provide every environment variable the production module reads, with safe dummy values where the real secret is not available. ## Trap 2: the exit status `check` raises `SystemCheckError`, and so exits with status 1, only for messages at or above the **fail level**, which defaults to `ERROR`. Nearly every deployment check is a **Warning**, so a run full of them still exits 0 and a pipeline step "passes". `--fail-level` accepts `CRITICAL`, `ERROR`, `WARNING`, `INFO` or `DEBUG`. For a release gate you want: ```bash python manage.py check --deploy --fail-level WARNING \ --settings=shopsite.settings.production ``` ## Reading the output Each reported line has the same shape: the object at fault (or `?` when there is none), the id in parentheses, the message, and an optional `HINT:` line. Messages are grouped under headings such as `ERRORS:` and `WARNINGS:`, and the footer summarises, for example `System check identified 3 issues (1 silenced).` The id is the stable handle: search the system check reference for it, cite it in the pull request that fixes it, and use it verbatim if you decide to silence it. ## Accepting a warning on purpose Some warnings are deliberate. A site behind a proxy that performs the HTTPS redirect may leave `SECURE_SSL_REDIRECT` off, and HSTS is often rolled out gradually. For those: 1. Add the id to `SILENCED_SYSTEM_CHECKS` **in the production settings module**, with a comment saying why. 2. Keep the list short and reviewed; each entry is a standing decision, not a mute button. 3. Re-read it at every Django upgrade, because new releases add new ids. Silenced messages are hidden, counted in the `(N silenced)` summary and ignored when deciding the exit status. ## What --deploy cannot tell you The deployment checks read **settings**. They cannot see whether TLS is actually terminated, whether the proxy forwards the right headers, whether static files are served, or whether backups exist. They are one automated step inside a release checklist, not the checklist itself. ## A sensible pipeline step 1. Install dependencies and set the production settings module plus its required environment variables. 2. Run `check --deploy --fail-level WARNING`. 3. Fail the build on a non-zero exit; the printed ids tell the author exactly what to fix or justify.
- check --deploy passes locally but the same command fails in CI with ImproperlyConfigured; what is usually going on?Locally it ran against development settings; in CI it loads the production module, which reads environment variables the build does not provide, so importing settings fails before any check runs. Provide those variables (dummy values where the real secret is unavailable) or run the step in an environment that has them.
- Why not simply silence every deployment warning the first run reports?`SILENCED_SYSTEM_CHECKS` hides a message and removes it from the exit-status decision, so a blanket list turns the gate off while still looking green. Silence only ids you have consciously accepted, in the production settings, with a reason, and review the list when upgrading Django because new ids appear.
saying these in an interview costs you the question
- check --deploy exits non-zero whenever it prints any warning
- Deployment checks also run automatically before runserver
- Running check --deploy with development settings is a meaningful gate
- A clean check --deploy proves the production setup is secure
- Silencing every reported id is the standard way to make the step pass