feat(deploy): add the reference deployment Compose project
CI / verify (pull_request) Successful in 54s
CI / infrastructure-smoke (pull_request) Successful in 21s
CI / migration-smoke (pull_request) Successful in 1m48s
CI / image-build (pull_request) Successful in 27s
CI / deploy-smoke (pull_request) Successful in 42s
CI / verify (pull_request) Successful in 54s
CI / infrastructure-smoke (pull_request) Successful in 21s
CI / migration-smoke (pull_request) Successful in 1m48s
CI / image-build (pull_request) Successful in 27s
CI / deploy-smoke (pull_request) Successful in 42s
Implement ADR 0044's reference deployment as a base Compose file plus overlays, so an installation that already runs a proxy can use the base alone. - compose.yaml: api, postgres, object-storage, and the operator-only migrate (profile ops, plan by default). No published port. api and migrate run init, read-only, tmpfs /tmp, cap_drop ALL, no-new-privileges. postgres is only on the internal data network. - TLS everywhere from one installation-private CA: PostgreSQL TLS 1.3 only, with pg_hba rejecting every non-TLS connection, and the API on verify-full via EKKLESIA_DATABASE_CA (PEM text). SeaweedFS serves S3 over HTTPS (ADR 0030 requires https in production), trusted by the API through NODE_EXTRA_CA_CERTS. No Core change. - compose.proxy.yaml + Caddyfile.example: Caddy pinned by digest, ACME HTTP-01 (DNS-01 documented), routes /, /api/*, /display/*, /assets/* to api and refuses everything else; the storage hostname preserves Host. The API trusts exactly the proxy's fixed /32 on an edge network whose dynamic range excludes the fixed addresses; Caddy overwrites X-Forwarded-For and strips caller-supplied forwarding headers. - compose.tunnel.yaml: cloudflared pinned by digest, token from its own env file; Caddy honors CF-Connecting-IP only from its fixed address. - Four per-service root-owned env files, examples with no values; scripts/init-tls.sh (idempotent, never overwrites a key) and scripts/gen-secrets.sh (prints only). - CI: pnpm deploy:config in ci:verify for all three combinations, and a deploy-smoke job (scripts/smoke-deploy.sh) proving readiness, the Dashboard, signed storage delivery, the client-address chain, initial owner sign-in/out, verify-full database TLS, and Doctor through the proxy, against disposable services. tests/deploy.test.ts pins the structural guarantees. - ADR 0044 (still Proposed) amended to match: API-to-storage HTTPS, DNS-01 needs a custom Caddy build and tunnel-only needs DNS-01, the tunnel's origin TLS settings live in the Cloudflare dashboard, port 80 serves HTTP-01, and secrets are one env file per service. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b593abe7a5
commit
167b4c4f14
@@ -0,0 +1,80 @@
|
||||
# The reference TLS proxy overlay (ADR 0044's `proxy` service). Use it on top of the base:
|
||||
#
|
||||
# docker compose -f compose.yaml -f compose.proxy.yaml up -d
|
||||
#
|
||||
# Caddy terminates TLS with publicly trusted ACME certificates and publishes the deployment's only
|
||||
# ports. It reaches `api` and `object-storage` on the `edge` network, which this overlay creates and
|
||||
# attaches them to; `postgres` is never on it.
|
||||
#
|
||||
# Client addresses. Caddy is the only peer the API trusts to say who the client is: this overlay
|
||||
# puts the API in trusted-proxy mode for exactly the proxy's fixed address, and the Caddyfile
|
||||
# overwrites X-Forwarded-For with the one address Caddy resolved. Because the address must be
|
||||
# fixed for "only the proxy" to be expressible, `edge` has an explicit subnet whose dynamic range is
|
||||
# its lower half; the proxy (and the tunnel connector, if used) take addresses from the upper half,
|
||||
# which Docker never hands to another container.
|
||||
|
||||
services:
|
||||
proxy:
|
||||
image: caddy:2.11.4-alpine@sha256:6aeddd44c3078b0f9a35206472a11420648a79c184603ef95957d0a20044cb2b
|
||||
restart: unless-stopped
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
ports:
|
||||
# 80 answers ACME HTTP-01 challenges and redirects everything else to HTTPS. Set
|
||||
# EKKLESIA_PUBLISH_ADDRESS to bind one host interface instead of all of them.
|
||||
- "${EKKLESIA_PUBLISH_ADDRESS:-0.0.0.0}:${EKKLESIA_HTTP_PORT:-80}:80"
|
||||
- "${EKKLESIA_PUBLISH_ADDRESS:-0.0.0.0}:${EKKLESIA_HTTPS_PORT:-443}:443"
|
||||
- "${EKKLESIA_PUBLISH_ADDRESS:-0.0.0.0}:${EKKLESIA_HTTPS_PORT:-443}:443/udp"
|
||||
environment:
|
||||
EKKLESIA_HOSTNAME: ${EKKLESIA_HOSTNAME:?EKKLESIA_HOSTNAME is required, for example ekklesia.example.org}
|
||||
EKKLESIA_STORAGE_HOSTNAME: ${EKKLESIA_STORAGE_HOSTNAME:?EKKLESIA_STORAGE_HOSTNAME is required, for example storage.ekklesia.example.org}
|
||||
EKKLESIA_ACME_EMAIL: ${EKKLESIA_ACME_EMAIL:?EKKLESIA_ACME_EMAIL is required for ACME account notices}
|
||||
EKKLESIA_TUNNEL_ADDRESS: ${EKKLESIA_TUNNEL_ADDRESS:-10.249.250.253}
|
||||
volumes:
|
||||
- ${EKKLESIA_CADDYFILE:-./Caddyfile}:/etc/caddy/Caddyfile:ro
|
||||
# The installation-private CA, so Caddy can verify object-storage's certificate.
|
||||
- ${EKKLESIA_TLS_DIR:-/etc/ekklesia/tls}/ca.crt:/etc/caddy/ekklesia-ca.crt:ro
|
||||
- proxy-data:/data
|
||||
- proxy-config:/config
|
||||
networks:
|
||||
edge:
|
||||
ipv4_address: ${EKKLESIA_PROXY_ADDRESS:-10.249.250.254}
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
object-storage:
|
||||
condition: service_healthy
|
||||
|
||||
api:
|
||||
networks:
|
||||
edge: {}
|
||||
environment:
|
||||
# Exactly one trusted peer: this proxy. Set here from the same variable that fixes the proxy's
|
||||
# address, so the two cannot drift apart. These override any value in ekklesia.env.
|
||||
EKKLESIA_CLIENT_ADDRESS_MODE: trusted-proxy
|
||||
EKKLESIA_CLIENT_ADDRESS_HEADER: x-forwarded-for
|
||||
EKKLESIA_CLIENT_ADDRESS_TRUSTED_PROXIES: ${EKKLESIA_PROXY_ADDRESS:-10.249.250.254}/32
|
||||
|
||||
object-storage:
|
||||
networks:
|
||||
edge: {}
|
||||
|
||||
networks:
|
||||
edge:
|
||||
ipam:
|
||||
config:
|
||||
- subnet: ${EKKLESIA_EDGE_SUBNET:-10.249.250.0/24}
|
||||
# Docker assigns dynamic addresses only from this range. Keep the fixed proxy and tunnel
|
||||
# addresses outside it.
|
||||
ip_range: ${EKKLESIA_EDGE_DYNAMIC_RANGE:-10.249.250.0/25}
|
||||
|
||||
volumes:
|
||||
proxy-data:
|
||||
proxy-config:
|
||||
Reference in New Issue
Block a user