build(project): publish app on loopback and segment Docker networks

Bind the published port to 127.0.0.1 so the app is reachable only through the
host reverse proxy, not on the LAN/WAN — a 0.0.0.0 publish would also bypass
ufw/firewalld, since Docker's DNAT rules sit ahead of the host firewall.

Split the stack onto two networks with deterministic bridge names: `web`
(dk-tanabata) for the public-facing side, and `backend` (dk-tanabata-bnd,
internal:true) for the private app↔DB tier. The DB sits only on `backend`, which
has no gateway, so it has no route off-host.

Document TRUSTED_PROXIES and the loopback publish in .env.example.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 14:51:57 +03:00
parent 99668ec0d8
commit 5a05bb86e1
2 changed files with 46 additions and 4 deletions
+11 -1
View File
@@ -14,7 +14,10 @@
# DATABASE_URL at host.docker.internal (see the Database section below).
COMPOSE_PROFILES=with-db
# Host port the app is published on. The container always listens on 42776.
# Host port the app is published on, bound to 127.0.0.1 (loopback) — a reverse
# proxy on the host fronts it (see README → Reverse proxy). The container always
# listens on 42776. To expose the app directly without a proxy, drop the
# "127.0.0.1:" prefix on the ports line in docker-compose.yml.
APP_PORT=42776
# ---------------------------------------------------------------------------
@@ -51,6 +54,13 @@ JWT_SECRET=change-me-to-a-random-32-byte-secret
JWT_ACCESS_TTL=15m
JWT_REFRESH_TTL=720h
# Reverse-proxy hops (comma-separated CIDRs/IPs) whose X-Forwarded-For is trusted,
# so the auth rate limiter sees real client IPs instead of the proxy's. The default
# covers loopback and the Docker bridge ranges a host nginx reaches the container
# through; widen/narrow it to match your proxy. Leave at the default for the
# standard "host nginx → 127.0.0.1" setup.
TRUSTED_PROXIES=127.0.0.1/32,::1/128,172.16.0.0/12
# Initial administrator, created on first startup if it does not yet exist.
# Changing the password later (via the API) is preserved across restarts.
ADMIN_USERNAME=admin