generated from bisco/codex-bootstrap
72 lines
3.4 KiB
Markdown
72 lines
3.4 KiB
Markdown
# ADR-0003: Optional Let's Encrypt termination for direct deployments
|
|
|
|
Date: 2026-06-24
|
|
|
|
Status: Accepted
|
|
|
|
## Context
|
|
|
|
Azione!Lab may be deployed either behind a load balancer that already terminates TLS or
|
|
directly on a public host. Running an ACME client unconditionally would duplicate edge
|
|
responsibilities in the first topology, while direct exposure still needs automated
|
|
certificate issuance and renewal.
|
|
|
|
## Decision
|
|
|
|
Add an opt-in Certbot service controlled by `LETSENCRYPT_ENABLED`. Docker Compose gives
|
|
that service zero replicas by default and one replica only when the value is `1`.
|
|
Certbot uses the HTTP-01 webroot method and shares separate challenge and certificate
|
|
volumes with NGINX.
|
|
|
|
NGINX always serves the ACME challenge path over HTTP. When TLS is enabled but no
|
|
certificate exists, application traffic remains available over HTTP. An entrypoint
|
|
watcher detects certificate creation or renewal, renders the HTTPS virtual host,
|
|
validates the configuration, reloads NGINX, and redirects non-ACME HTTP traffic to
|
|
HTTPS. No Docker socket or container-control privilege is required.
|
|
|
|
The direct-deployment operator is responsible for public DNS, inbound ports 80 and 443,
|
|
a valid contact email, and selecting the staging or production ACME endpoint. Behind a
|
|
load balancer, Certbot stays disabled and the load balancer owns certificates.
|
|
|
|
## Consequences
|
|
|
|
- One Compose definition supports both deployment topologies.
|
|
- Direct deployments gain automated issue and renewal without manual certificate copy.
|
|
- The first direct request can use HTTP until issuance completes; operators must not
|
|
publish sensitive workflows before TLS has been verified.
|
|
- Certificate and challenge volumes add persistent operational state.
|
|
- HTTP-01 cannot issue when port 80 or DNS is controlled by another edge.
|
|
|
|
## Alternatives considered
|
|
|
|
- Always running Certbot was rejected because it conflicts with load-balancer-managed
|
|
certificates and creates unnecessary ACME traffic.
|
|
- Separate Compose files per topology were rejected as avoidable configuration drift
|
|
for this small stack.
|
|
- Giving Certbot access to the Docker socket to reload NGINX was rejected because that
|
|
privilege is disproportionate; NGINX can safely watch its read-only certificate
|
|
volume.
|
|
- DNS-01 was not selected because it requires provider-specific credentials and
|
|
dependencies. It remains a future option for wildcard certificates or closed port 80.
|
|
|
|
## Security impact
|
|
|
|
The certificate private key is writable only by Certbot and read-only to NGINX. Certbot
|
|
uses a read-only root filesystem, temporary runtime mounts, `no-new-privileges`, and no
|
|
Docker socket. Production must restrict application ports and the proxy trust boundary;
|
|
Django accepts the secure forwarded-protocol header for load-balancer deployments.
|
|
|
|
## Operational impact
|
|
|
|
Operators must monitor both Certbot issue/renew logs and NGINX reload logs, protect the
|
|
certificate volume, and test DNS/firewall changes with the staging CA. Renewals are
|
|
checked every 12 hours and NGINX detects certificate changes every 30 seconds by
|
|
default. These intervals are configurable.
|
|
|
|
## Rollback
|
|
|
|
Set `LETSENCRYPT_ENABLED=0` and recreate the stack to stop Certbot while retaining
|
|
certificate state. Terminate TLS at a load balancer if public service must continue.
|
|
The certificate volumes can be removed only after confirming they are no longer needed;
|
|
database and media volumes are independent.
|