Fold files./filestash into the docs that carry the service list, the vhost
map and the deploy procedure — README stack table, 00-overview, 02-caddy,
12-build-order, 14-deploy, and the deploy skill.
Two real gaps the filestash deploy exposed, now guardrails in the skill:
- update-all.sh never touches host nginx, so a NEW SUBDOMAIN silently 404s
after deploy. It needs issue-letsencrypt.sh + render-nginx.sh --install.
- a new service with ${FOO:?} guards fails the WHOLE compose file, so a
missing .env var means `up` starts nothing — and --hard has already run
`down`. Check the host's .env BEFORE tearing the stack down.
Also: filestash/config.json is a single-file bind mount re-rendered every
run, so a rolling update-all leaves it on a stale inode (same trap as the
caddy conf) — force-recreate after any FILES_* change.
Correct the cochi divergence section: `git diff HEAD` on the host is now
empty (verified this deploy — the ff-pull succeeded). The documented
docker-compose.yml/package.json divergence was converged; only untracked
dnsmasq/, caddy-root-ca.crt and a stale pack/ remain. The stash-pull-pop
procedure it prescribed is no longer needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2.8 KiB
Caddy — internal router behind host nginx
Summary
caddy is the stack's internal HTTP router, sitting behind the host's own
nginx (which terminates TLS — see 15-letsencrypt.md). caddy is published only
on a localhost-only port (CADDY_HTTP_BIND=127.0.0.1, CADDY_HTTP_PORT=8880);
nginx reverse-proxies every public vhost to it by Host header. Roles:
- Reverse proxy —
auth.→ drasl,avatar.→ nmsr,status.→ uptime-kuma,files.→ filestash. - Static — apex serves the landing page +
/launcher/downloads;distribution.serves a static site from another repo.
Image: caddy:alpine. caddy natively reads {$BASE_DOMAIN} env placeholders —
no template render needed.
Network aliases (internal resolution)
caddy:
networks:
mcnet:
aliases:
- "auth.${BASE_DOMAIN}"
Lets minecraft reach http://auth.ulicraft.net/authlib-injector from inside
the stack.
conf.d snippets
The Caddyfile imports per-vhost snippets from caddy/conf.d/:
00-core.caddy—auth.→ drasl.10-static.caddy— apex landing (/srv/www) +/launcher/*(/srv/launcher).30-distribution.caddy—distribution.→/srv/distribution(static, fromDISTRIBUTION_WEB_ROOT, another repo).40-avatar.caddy—avatar.→nmsr:8080(skin/avatar renderer).50-status.caddy—status.→uptime-kuma:3001(status page).60-files.caddy—files.→filestash:8334(player file share,20-files.md).
All vhosts are plain http://…; TLS is nginx's job. Mounts: ./www:/srv/www:ro,
./launcher:/srv/launcher:ro, ${DISTRIBUTION_WEB_ROOT}:/srv/distribution:ro.
Don't rewrite the
Hostheader forfiles.— filestash rejects any request whose Host doesn't match its configuredgeneral.host, with "only traffic from X is allowed" (a 403 that reads like an auth failure). nginx forwards the original Host and caddy passes it through untouched. Keep it that way.
Notes / risks
- drasl behind a proxy: with matching
BaseURLand nginx terminating TLS it is fine; nginx passes the original Host through to caddy → drasl. /launcher/is a sibling mount (/srv/launcher), not nested under the read-only/srv/www;handle_pathstrips the prefix.
Tasks
caddyservice indocker-compose.yml, published127.0.0.1:${CADDY_HTTP_PORT}:80- Pass
BASE_DOMAIN+DISTRIBUTION_WEB_ROOTinto caddy env - Caddyfile imports
conf.d/*.caddy - Network alias for
auth.subdomain - Mount
./www,./launcher,${DISTRIBUTION_WEB_ROOT} - Confirm reverse_proxy to drasl works (auth web UI loads, Yggdrasil API responds)
- Verify internal alias resolution from
minecraftcontainer (curl authlib-injector) - Verify nmsr + uptime-kuma proxied (
avatar.renders,status.loads)