Compose: make Caddy the default TLS front end (TTX/EBS parity)#283
Merged
Conversation
The Caddy reverse proxy moves from the opt-in `tls` profile to the default `docker compose up` path, matching the sibling IcebergTTX/EBS stacks: Caddy publishes the only public ports (:80/:443, +443/udp for HTTP/3) with automatic HTTPS (local CA for `localhost`, Let's Encrypt for a real ICEBERG_DOMAIN), while the app's plain-HTTP :8000 publish stays loopback-only as a debug side door. Hardening mirrors TTX: a one-shot caddy-init chowns the cert/config volumes so caddy runs non-root (uid 1000) with cap_drop ALL + NET_BIND_SERVICE on a read-only rootfs; the image pin moves to caddy:2-alpine. The Caddyfile gains upstream dial/response-header timeouts so a stalled worker returns 502 instead of holding client connections. Also adds the `docker-compose` Dependabot ecosystem (EBS parity) so the postgres/redis/caddy compose pins get update PRs — the Dockerfile `docker` ecosystem only reads FROM lines. The compose-render test drops the profile flag and now asserts the caddy service's port/user/capability/dependency invariants. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
IcebergAutoReview
Verdict: approve
The clean, single-commit change correctly makes hardened Caddy the default TLS frontend while preserving loopback-only app access. Documentation, Dependabot configuration, Caddy timeouts, Compose dependencies, ports, capabilities, and tests are consistent. No blocking correctness or security issues found.
Findings
- No blocking findings.
Validation
- Reviewed complete main...HEAD diff and affected deployment context.
- Verified clean ancestry and scope against main.
- git diff --check main...HEAD passed.
- Targeted pytest could not start because local dependencies are unavailable (FastAPI missing); uv also could not create an environment on the read-only workspace. Current-head GitHub CI is green.
Residual risks / optional notes
- The complete non-root Caddy startup path is supported by the reported manual stack test but is not exercised by PR CI beyond resolved Compose configuration assertions.
Automated review by Codex 872d986f6445 using IcebergAutoReview.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
--profile tlsto the defaultdocker compose uppath, matching the sibling IcebergTTX/EBS stacks — it publishes the only public ports (:80/:443,+443/udpHTTP/3) with automatic HTTPS (local CA forlocalhost, Let's Encrypt for a realICEBERG_DOMAIN); the app's plain-HTTP:8000stays published on loopback only as a local/debug side door (docker-compose hardcodes ICEBERG_ENVIRONMENT=dev, overriding .env — the documented TLS/production path silently keeps the dev-login bypass enabled #163 invariant unchanged).caddy-initchowns the cert/config volumes socaddyruns non-root (uid 1000) withcap_drop: ALL+NET_BIND_SERVICEon a read-only rootfs; image pin movescaddy:2→caddy:2-alpine.deploy/Caddyfile: upstreamdial_timeout/response_header_timeoutso a stalled app worker returns 502 instead of holding client connections open.docker-composeecosystem (EBS parity) so the postgres/redis/caddy compose pins get update PRs — thedockerecosystem only reads DockerfileFROMlines.Testing
tests/test_prod_hardening.py::test_compose_prod_overrides_and_loopback_portdrops the profile flag and now asserts the caddy service's port/user/capability/dependency invariants — passes.caddy-initruns and exits, caddy starts after the app is healthy;http://localhost/→ 308 →https://localhost/, portal login renders over TLS,/healthz200 through the proxy. The read-only-rootfs "failed to install root certificate" log line is expected (Caddy trying to trust its own CA inside the container), same as TTX.🤖 Generated with Claude Code