feat: add optional letsencrypt tls

This commit is contained in:
bisco
2026-06-24 11:06:33 +02:00
parent 1e3685bab8
commit 719cdce9c1
19 changed files with 561 additions and 62 deletions
+54 -13
View File
@@ -18,16 +18,17 @@ 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.
`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`, and `NGINX_HTTP_PORT`. PostgreSQL bootstrap variables are also
documented in `.env.example`. Do not use the example credentials outside local
development.
`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
@@ -37,18 +38,58 @@ The PostgreSQL Compose service is `db`, so new `DATABASE_URL` values use `db:543
The internal `postgres` alias is retained only for compatibility with existing local
`.env` files and should not be used in new configuration.
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.
## 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:
```dotenv
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:
```bash
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 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.
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
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.
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.