trails/infrastructure/Caddyfile
Ullrich Schäfer 9ed6b7dd1d fix(infra): move Caddy metrics to global option
Caddy 2.9 deprecated the nested `servers { metrics }` form; every
deploy logs a deprecation warning. The global `metrics` option is the
replacement and emits the same Prometheus metrics on the admin
endpoint, so the existing scrape config is unaffected.

Validated with `caddy validate` against the caddy:2 image — config
valid, no deprecation warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:30:17 +02:00

118 lines
4.5 KiB
Caddyfile
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
metrics
admin 0.0.0.0:2019
}
(security_headers) {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "camera=(), microphone=(), geolocation=()"
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' blob:; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://*.tile.openstreetmap.org; connect-src 'self' wss: https://*.sentry.io https://*.ingest.de.sentry.io; font-src 'self';"
}
}
(block_scanners) {
@scanners path /.env* /.git* /wp-* /wp-admin* /admin* /config.* /backup* /.aws* /.docker* /composer* /vendor* *.php *.asp
respond @scanners 403
}
{$DOMAIN:trails.cool} {
import security_headers
import block_scanners
log {
output stdout
format json
}
reverse_proxy journal:3000 {
# During an `apps` deploy the journal container is briefly down
# (~1030s) while compose swaps containers. Without these,
# Caddy returns 502 immediately and the `caddy-502-rate` alert
# trips on every deploy. With them, Caddy holds and retries
# against the upstream for up to 30s — restart becomes
# invisible to clients. A real outage longer than 30s still
# 502s and correctly trips the alert.
lb_try_duration 30s
lb_try_interval 250ms
}
}
www.{$DOMAIN:trails.cool} {
redir https://{$DOMAIN:trails.cool}{uri} permanent
}
grafana.internal.{$DOMAIN:trails.cool} {
reverse_proxy grafana:3000
}
planner.{$DOMAIN:trails.cool} {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "camera=(), microphone=(), geolocation=()"
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' blob:; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://*.tile.openstreetmap.org; connect-src 'self' wss: https://{$DOMAIN:trails.cool} https://*.sentry.io https://*.ingest.de.sentry.io; font-src 'self';"
}
import block_scanners
log {
output stdout
format json
}
reverse_proxy planner:3001 {
# Same rationale as the journal block — see the comment there.
lb_try_duration 30s
lb_try_interval 250ms
}
}
# ── Staging ──────────────────────────────────────────────────────────────
# Persistent staging instance. CSP allow-lists hardcode `staging.trails.cool`
# rather than `{$DOMAIN}` because the Caddy container runs with the
# production DOMAIN env (`trails.cool`); staging blocks need their own domain
# baked in. Upstreams are reached via `host.docker.internal` because the
# staging compose project publishes its containers on the host's loopback
# (127.0.0.1:3100/3101) rather than joining the production Caddy network.
staging.{$DOMAIN:trails.cool} {
import security_headers
import block_scanners
log {
output stdout
format json
}
reverse_proxy host.docker.internal:3110 {
lb_try_duration 30s
lb_try_interval 250ms
}
}
planner.staging.{$DOMAIN:trails.cool} {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "camera=(), microphone=(), geolocation=()"
# connect-src includes wss + https://*.staging so PR-preview journals
# (pr-N.staging.trails.cool) can use this shared planner. PR previews
# are journal-only; this is the planner they all talk to.
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' blob:; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://*.tile.openstreetmap.org; connect-src 'self' wss://*.staging.{$DOMAIN:trails.cool} https://*.staging.{$DOMAIN:trails.cool} https://staging.{$DOMAIN:trails.cool} https://*.sentry.io https://*.ingest.de.sentry.io; font-src 'self';"
}
import block_scanners
log {
output stdout
format json
}
reverse_proxy host.docker.internal:3111 {
lb_try_duration 30s
lb_try_interval 250ms
}
}
# Per-PR preview snippets are written by cd-staging.yml into
# /etc/caddy/sites/pr-<N>.caddyfile and picked up here on Caddy reload. The
# glob is allowed to match nothing — Caddy treats an empty match as a no-op.
import /etc/caddy/sites/*.caddyfile