🛡️ Security
What a project does for you in production, and the checklist for what is left to you.
What is set up
HTTPS. Caddy gets and renews the certificate, redirects HTTP to HTTPS
and sends Strict-Transport-Security for a year. Django marks the session
and CSRF cookies Secure for an https SITE_URL.
Headers. Every response has X-Content-Type-Options: nosniff,
Referrer-Policy: strict-origin-when-cross-origin and
X-Frame-Options: DENY, and no Server header. The frontend's pages have a
Content Security Policy with hashes for its own scripts and styles: no
inline script runs unless Astro built it. Images and video may come only
from the site and from S3_PUBLIC_URL. Uploaded files in /media are
served with script-src 'none', so an uploaded HTML or SVG file cannot run
scripts on your domain.
Containers. The backend, worker and frontend containers run as
a user without privileges. They, Caddy and Valkey run on a read-only file
system with only /tmp and their volumes writable, without Linux
capabilities (Caddy keeps only the one to bind ports 80 and 443) and with
no-new-privileges. Each has a memory limit. Only
Caddy has public ports; development ports listen on 127.0.0.1 only.
Secrets. The frontend never receives the full-access API key or the
database password: it reads content with the read-only key. init and
production generate every secret, and production never reuses one from
development.
Checks. The backend runs python manage.py check --deploy before it
starts and stops on an error, such as debug on for an https site or the
two API keys being the same. Warnings appear in its log. DjangoStack's own
checks are in Configuration.
Throttling. The public form routes are limited to 10 requests a
minute per client (PUBLIC_WRITE_THROTTLE_RATE). NUM_PROXIES makes the
limit apply to the client, not to the proxy. They also need a token of the
captcha, which the visitor's browser earns with a proof of
work.
Checklist
Before the site goes live:
-
.envon the server came fromdstack production, and is not in git.chmod 600 .env. -
dstack checkshows no issues. In production it runs Django's deploy checks. - The admin password is changed from the generated one, or kept in a
password manager, and
ADMIN_EMAILis a real address for password resets. -
EMAIL_URLis your SMTP server; a password reset mail arrives. - The server's firewall allows only SSH, 80 and 443. Docker's published
ports bypass
ufw; only Caddy publishes any. - SSH accepts keys only, and the server installs security updates
(
unattended-upgradeson Debian and Ubuntu). - A media bucket, if any, allows public reads but not public listing or writes. A backup bucket is not public at all.
-
BACKUP_S3_BUCKETis set, or the server's disk is backed up, and you have restored a backup once. See Backups. -
SEED_DEMO_CONTENT=0if the demo content should not appear.
Over time:
- Update the images:
git pullbrings new versions of the project's dependencies when you upgrade them, anddstack updategets new Postgres, Valkey and Caddy images of the same major version. - Rotate a secret that may have leaked:
dstack secretprints a new one. Change it in.envand rundstack run. - Check the advisories of Django and the frontend's dependencies:
uv lock --upgradeinbackend/andnpm updateinfrontend/, then deploy.
HSTS for subdomains
SECURE_HSTS_INCLUDE_SUBDOMAINS=1 and SECURE_HSTS_PRELOAD=1 extend HSTS
to every subdomain and to the browsers' preload list. Turn them on only when
every subdomain of the domain serves HTTPS: a preload is hard to undo.