4.1 KiB
Deployment
Local environment
Copy .env.example to .env, replace the development placeholders, then run:
cp .env.example .env
docker compose up --build -d
docker compose ps
docker compose exec backend python manage.py seed_demo
The backend applies migrations and collects static files before starting Gunicorn. All services have health checks; wait for healthy status before opening the site.
Add 127.0.0.1 azionelab.org to the local hosts file, then use
http://azionelab.org:8080. NGINX binds to loopback port 8080 by default and routes
the domain to Astro or Wagtail. Their direct loopback ports 4321 and 8000 remain
available for diagnostics. PostgreSQL is available only on the Compose network.
postgres_data and media_data are persistent named volumes. Local HTTPS is disabled
by default; letsencrypt_data and certbot_challenges remain empty unless used.
The stack uses explicit PostgreSQL 16.9, Python 3.12.12, Node.js 22.20, and NGINX 1.30.0
image versions. Containers are not privileged and use no-new-privileges.
Required runtime variables are DATABASE_URL, DJANGO_SECRET_KEY, DJANGO_DEBUG,
DJANGO_ALLOWED_HOSTS, WAGTAILADMIN_BASE_URL, PUBLIC_CMS_API_URL,
NGINX_BIND_ADDRESS, NGINX_HTTP_PORT, and NGINX_HTTPS_PORT. The optional certificate
variables and PostgreSQL bootstrap variables are documented in .env.example. Do not
use the example credentials outside local development.
WAGTAILADMIN_BASE_URL must be browser-reachable because it is used for media URLs.
PUBLIC_CMS_API_URL must be reachable by Astro; within Compose it is
http://backend:8000.
The PostgreSQL Compose service is db, so new DATABASE_URL values use db:5432.
The internal postgres alias is retained only for compatibility with existing local
.env files and should not be used in new configuration.
TLS deployment modes
When an external load balancer terminates TLS, leave LETSENCRYPT_ENABLED=0. The
certbot service then has zero replicas and NGINX serves HTTP to the trusted internal
network. Configure the load balancer to set X-Forwarded-Proto: https, keep application
ports private, and restrict proxy access to the load balancer network.
For direct exposure, the HTTP-01 challenge requires public DNS for
LETSENCRYPT_DOMAIN to resolve to this host and inbound TCP ports 80 and 443 to reach
NGINX. Use a real operator address and production-safe Django settings:
LETSENCRYPT_ENABLED=1
LETSENCRYPT_DOMAIN=azionelab.org
LETSENCRYPT_EMAIL=operator@example.org
LETSENCRYPT_STAGING=1
NGINX_BIND_ADDRESS=0.0.0.0
NGINX_HTTP_PORT=80
NGINX_HTTPS_PORT=443
WAGTAILADMIN_BASE_URL=https://azionelab.org
DJANGO_DEBUG=false
DJANGO_ALLOWED_HOSTS=azionelab.org
Then run docker compose up --build -d and inspect docker compose logs certbot proxy.
NGINX serves HTTP until a certificate exists, then reloads and redirects normal HTTP
requests to HTTPS. The ACME path remains available over HTTP for renewal.
Use the Let's Encrypt staging CA first. Before switching to the production CA, stop the stack and remove only the staging certificate volume after checking its exact Compose project name:
docker compose down
docker volume ls --filter label=com.docker.compose.volume=letsencrypt_data
docker volume rm PROJECT_letsencrypt_data
Set LETSENCRYPT_STAGING=0, restart, and verify the certificate issuer in a browser or
TLS inspection tool. Never use docker compose down --volumes on an environment whose
database, media, or certificates must be retained.
Production boundary
The Compose stack remains a minimal deployment base. A public environment still needs production static/media serving, restricted admin access, managed secrets, off-host backups, monitoring, firewall rules, and an explicit domain/allowed-host policy.
Rollback
Set LETSENCRYPT_ENABLED=0 to disable the certificate service without deleting
certificates, or terminate TLS at the load balancer. Revert the application commit and
rebuild images for a full rollback. Keep database, media, and certificate volumes unless
deletion is intentional. Schema rollback must be evaluated per Django migration; take
coordinated database and media backups first.