# Deployment ## Local environment Copy `.env.example` to `.env`, replace the development placeholders, then run: ```bash 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. 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`, and `NGINX_HTTP_PORT`. PostgreSQL bootstrap variables are also 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 local virtual host is HTTP-only. Production must publish NGINX through the intended network interface, configure DNS for `azionelab.org`, terminate TLS, and prevent direct external access to application ports. ## Production boundary The Compose stack is a local development deployment. A public environment still needs TLS termination, production static/media serving, restricted admin access, managed secrets, off-host backups, monitoring, and an explicit domain/allowed-host policy. ## Rollback Revert the application commit and rebuild images. Keep the database and media volumes unless content deletion is intentional. Schema rollback must be evaluated per Django migration; take coordinated database and media backups first.