Let the gateway write its certificate and config as the user it runs as

Caddy runs as MCP_UID but its /data volume belonged to root (or to the user of an earlier run) and
its /config tmpfs started out owned by root, so it could not store a certificate or even create its
config folder: port 80 answered, port 443 failed the TLS handshake.

The tmpfs now takes the uid and gid, and a one-shot caddy-data-init service gives the /data volume
to the same user each time the stack starts; the gateway waits for it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucio Lelii 2026-10-02 18:30:40 +02:00
parent 6a501e91c0
commit 7f9fae3538
1 changed files with 35 additions and 2 deletions

View File

@ -230,7 +230,11 @@ services:
# /config only. /data is a volume instead, because Caddy keeps the certificate and its ACME
# account key there: on tmpfs both would be thrown away at every restart, and asking the CA
# for a fresh certificate each time runs into its duplicate-issuance limit within a week.
- /config:rw,noexec,nosuid,size=16m
#
# With the uid and gid of the user it runs as: a tmpfs starts out owned by root, and Caddy
# could not even create /config/caddy. Compose's long tmpfs syntax has no uid/gid field, so
# this is the raw mount-options string, as for the proxy's /var/log/squid below.
- /config:rw,noexec,nosuid,size=16m,uid=${MCP_UID:-10001},gid=${MCP_GID:-10001}
volumes:
- type: bind
source: ${MCP_GATEWAY_CADDYFILE:-./mcp-stack.Caddyfile}
@ -262,10 +266,39 @@ services:
- "127.0.0.1:${BROWSER_MCP_PORT:-3103}:3103"
- "127.0.0.1:${MINIO_MCP_PORT:-3104}:3104"
networks: [gateway, coding-control, dev-server-control, workspace-browser, minio-control]
depends_on: [coding-agent-mcp, dev-server-mcp, browser-mcp, minio-mcp]
depends_on:
caddy-data-init:
condition: service_completed_successfully
coding-agent-mcp:
condition: service_started
dev-server-mcp:
condition: service_started
browser-mcp:
condition: service_started
minio-mcp:
condition: service_started
restart: unless-stopped
logging: *default-logging
# Gives Caddy's /data volume to the user the gateway runs as, every time the stack starts. A
# named volume belongs to whoever created it - root, or the user of an earlier run - and the
# gateway, running as MCP_UID, then cannot write the certificate or the ACME account key: it
# answers on port 80 but never gets a certificate, and port 443 fails the TLS handshake. Runs as
# root with nothing but CAP_CHOWN, no network, and exits.
caddy-data-init:
image: caddy:2
user: "0:0"
command: ["chown", "-R", "${MCP_UID:-10001}:${MCP_GID:-10001}", "/data"]
read_only: true
cap_drop: [ALL]
cap_add: [CHOWN]
security_opt: [no-new-privileges:true]
network_mode: none
volumes:
- caddy-data:/data
restart: "no"
logging: *default-logging
networks:
gateway:
coding-control: