feat: mark proxy-originated errors and pin container DNS
Build Admin / Build image (push) Successful in 28s

- handle_errors sets X-Proxy-Error (dial|upstream) and X-Proxy-Error-Detail
  so clients with several proxies can fail over when the upstream was
  never reached, instead of treating the proxy's 502 as the upstream's
- explicit dns in compose: the container no longer inherits a host
  resolver that may disappear (systemd-resolved was switched off on the
  VM and every lookup failed until a restart)
- strip X-Proxy-Token from Caddy logs (it was logged in clear text)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
vasyanskandClaude Opus 5.5 committed 2026-10-27 19:05:04 +07:00
1 parent e84b79dda0
commit 4fccfb9089
3 files changed
+50 -1

No files matched your search

+42
View File
@@ -5,6 +5,18 @@
{
email {$ACME_EMAIL}
admin off
# The error log dumps request headers, and Caddy redacts only
# Authorization/Cookie on its own: X-Proxy-Token used to land in
# `docker logs` in clear text on every failed upstream call.
log default {
format filter {
wrap json
fields {
request>headers>X-Proxy-Token delete
}
}
}
}
# --- Access control (generated by docker-entrypoint.sh from env) ---
@@ -44,8 +56,38 @@
}
}
# --- Proxy-originated errors ---
# Errors produced by the proxy itself (the upstream answered nothing) are
# marked with X-Proxy-Error so that a client with several proxies can tell
# them apart from an upstream's own 5xx and fail over:
# dial — the upstream was never reached (DNS, connect, no route): the
# request did not leave this host, retrying elsewhere is safe
# even for non-idempotent calls
# upstream — anything else (timeouts, broken responses): the upstream may
# have processed the request
# X-Proxy-Error-Detail carries the reason. Only clients that passed the
# guards ever get here (the guards respond directly, without an error).
# Note: handled errors are logged by Caddy at DEBUG level only — the reason
# is visible to the client via X-Proxy-Error-Detail.
(proxy_errors) {
handle_errors {
@dial `{err.message}.startsWith("dial ")`
handle @dial {
header X-Proxy-Error dial
header X-Proxy-Error-Detail "{err.message}"
respond `{"error":"proxy_dial_failed"}` {err.status_code}
}
handle {
header X-Proxy-Error upstream
header X-Proxy-Error-Detail "{err.message}"
respond `{"error":"proxy_upstream_failed"}` {err.status_code}
}
}
}
(routes) {
import /etc/caddy/matchers.caddy
import proxy_errors
route {
# Health check — no auth, evaluated before the guards