Fix Caddy reverse-proxy target for host-network services

Caddy runs in its own container on the caddy_net bridge network, so
"localhost" in a Caddyfile site block resolves to Caddy's own
container — never the host, and never a sibling container. That
broke every reverse proxy pointed at a network_mode: host service
(confirmed live with asterisk-do's web admin): once nothing else
(like a forward_auth redirect) intercepted the request first, Caddy
couldn't actually reach the upstream.

- services/caddy.sh: add extra_hosts so host.docker.internal resolves
  inside the Caddy container (Linux Docker needs this explicitly —
  it's automatic only on Docker Desktop).
- lib/common.sh's configure_caddy_for_service: bare-port upstreams
  (its documented "host-network service" case) now target
  host.docker.internal instead of localhost.
- services/asterisk-do.sh: its self-contained Caddy block (doesn't go
  through configure_caddy_for_service) gets the same fix for local
  Caddy, and now correctly targets the droplet's public IP instead of
  localhost for the remote-Caddy snippet case, which had the same bug.

services/asterisk.sh needs no direct change — it already goes through
configure_caddy_for_service, so it inherits the fix.
This commit is contained in:
Claude
2026-07-20 04:11:24 +00:00
parent d6261de1e7
commit 6c48bdc707
3 changed files with 25 additions and 5 deletions
+9 -4
View File
@@ -416,12 +416,17 @@ configure_caddy_for_service() {
local SERVICE_NAME="$1" SERVICE_UPSTREAM="$2" DEFAULT_SUBDOMAIN="$3" EXTRA_CONFIG="${4:-}"
# Derive the proxy upstream and a port number for display messages.
# Plain number → localhost:PORT (host-network or legacy services)
# name:port → used as-is (preferred: service on shared caddy_net)
# Plain number → host.docker.internal:PORT (host-network or legacy
# services — Caddy itself runs in its own container on
# caddy_net, a bridge network, so "localhost" here would
# resolve to Caddy's own container, not the host. Requires
# the extra_hosts entry set in services/caddy.sh's compose
# file — see the comment there.)
# name:port → used as-is (preferred: service on shared caddy_net)
local _UPSTREAM _DISPLAY_PORT
case "$SERVICE_UPSTREAM" in
*:*) _UPSTREAM="$SERVICE_UPSTREAM"; _DISPLAY_PORT="${SERVICE_UPSTREAM##*:}" ;;
*) _UPSTREAM="localhost:$SERVICE_UPSTREAM"; _DISPLAY_PORT="$SERVICE_UPSTREAM" ;;
*:*) _UPSTREAM="$SERVICE_UPSTREAM"; _DISPLAY_PORT="${SERVICE_UPSTREAM##*:}" ;;
*) _UPSTREAM="host.docker.internal:$SERVICE_UPSTREAM"; _DISPLAY_PORT="$SERVICE_UPSTREAM" ;;
esac
# ── Determine Caddy mode ──────────────────────────────────────────────────
+10 -1
View File
@@ -769,12 +769,21 @@ ENV
local _CADDY_MODE="local"
[[ ! -d "$DOCKER_DIR/caddy" ]] && [[ -n "${CADDY_REMOTE_HOST:-}" ]] && _CADDY_MODE="remote"
# Asterisk runs with network_mode: host, so whatever proxies to it
# needs a way to reach the host, not "localhost" (which resolves
# to the proxying container's own netns). A local Caddy container
# reaches the host via host.docker.internal (wired up in
# services/caddy.sh's compose file); a remote Caddy machine needs
# this droplet's actual public IP instead.
local _PROXY_TARGET="host.docker.internal:${WEB_ADMIN_PORT_VAL}"
[[ "$_CADDY_MODE" == "remote" ]] && _PROXY_TARGET="${PUBLIC_IP}:${WEB_ADMIN_PORT_VAL}"
local _SITE_BLOCK
_SITE_BLOCK="$(cat << CADDY_BLOCK
# Asterisk Web Admin
${DOMAIN_NAME} {
reverse_proxy localhost:${WEB_ADMIN_PORT_VAL}
reverse_proxy ${_PROXY_TARGET}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
+6
View File
@@ -267,6 +267,12 @@ services:
- ACME_AGREE=true
labels:
- "io.podman.annotations.label/crowdsec.enable=true"
# Lets Caddyfile blocks reach services that use network_mode: host
# (e.g. asterisk/asterisk-do) via "host.docker.internal:PORT" — Caddy
# itself is on the caddy_net bridge network below, so plain "localhost"
# in a site block resolves to Caddy's own container, not the host.
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- caddy_net