Files
learning-platform/Caddyfile
RaymondVerhoef 80f738ddcb
All checks were successful
On Pull Request to Main / test (pull_request) Successful in 39s
On Pull Request to Main / publish (pull_request) Successful in 1m7s
On Pull Request to Main / deploy-dev (pull_request) Successful in 3m6s
fix: valid Caddyfile handle_errors + frontend health-gate in deploys (#20)
The learning-platform (Caddy) container crash-looped since this morning's
89d3395: `Content-Type application/json` is not a valid subdirective of
`respond` — a Caddyfile parse error aborts Caddy at startup, taking the
whole app offline for authenticated users while every CI run stayed green.
Verified with caddy v2.10.0: "unrecognized subdirective 'Content-Type',
at Caddyfile:53"; the fixed file validates clean.

CI was blind to this twice over: test.yml swaps the real Caddyfile for
Caddyfile.test in the test image, and the deploy health-gate from #18
only covers PocketBase.

- Caddyfile: set Content-Type via a `header @api` directive before the
  `respond`; behaviour of the JSON error response is unchanged
- infra/*/site/deploy-playbook.yml: frontend health-gate (docker exec
  wget --spider on the container) with log dump + abort on failure, and
  an always-on post-deploy smoke report (compose ps, proxy health,
  team_members auth-methods, PocketBase log tail) for visibility behind
  the auth perimeter

Closes #20

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 14:20:15 +02:00

68 lines
2.1 KiB
Caddyfile

:80 {
encode gzip zstd
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
X-XSS-Protection "1; mode=block"
Referrer-Policy "strict-origin-when-cross-origin"
Content-Security-Policy "default-src 'self'; connect-src 'self' https://api.github.com https://raw.githubusercontent.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com"
Permissions-Policy "geolocation=(), microphone=(), camera=()"
}
handle /api/anthropic/* {
uri strip_prefix /api/anthropic
reverse_proxy https://api.anthropic.com {
header_up Host api.anthropic.com
header_up x-api-key "{$ANTHROPIC_API_KEY}"
header_up anthropic-dangerous-direct-browser-access "true"
}
}
handle /api/* {
reverse_proxy pocketbase-learning:8090
}
handle /_/* {
reverse_proxy pocketbase-learning:8090
}
handle {
root * /srv
@static {
path *.css *.js *.png *.jpg *.jpeg *.gif *.webp *.svg *.woff *.woff2 *.ttf
}
header @static Cache-Control "public, max-age=31536000, immutable"
@html {
path *.html /
}
header @html Cache-Control "public, max-age=0, must-revalidate"
try_files {path} /index.html
file_server
}
handle_errors {
# Don't mask API errors with the SPA shell — return a proper
# JSON error so the frontend can display a meaningful message.
# NOTE: `respond` only accepts `body`/`close` subdirectives; the
# Content-Type must be set via a separate `header` directive. An
# invalid subdirective is a Caddyfile parse error and crash-loops
# the container at startup (issue #20).
@api path /api/*
header @api Content-Type application/json
respond @api `{"code":{err.status_code},"message":"Backend unavailable"}` {err.status_code}
rewrite * /index.html
file_server
}
log {
output stdout
format console
}
}