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:
+11
-1
@@ -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
|
||||
|
||||
+35
-3
@@ -35,10 +35,23 @@ services:
|
||||
environment:
|
||||
STATIC_DIR: /app/static
|
||||
|
||||
# The container always listens on 42776 (Dockerfile default); APP_PORT only
|
||||
# changes the host-published port.
|
||||
# Published on loopback only: a reverse proxy on the host (e.g. nginx) fronts
|
||||
# the app and proxies to 127.0.0.1:${APP_PORT}. Binding to 127.0.0.1 keeps the
|
||||
# app off the LAN/WAN — a plain "PORT:42776" would publish on 0.0.0.0 and, since
|
||||
# Docker's DNAT rules sit ahead of the host firewall, bypass ufw/firewalld. The
|
||||
# container always listens on 42776 (Dockerfile default); APP_PORT only changes
|
||||
# the host-published port. Drop the 127.0.0.1 prefix if exposing it directly.
|
||||
ports:
|
||||
- "${APP_PORT:-42776}:42776"
|
||||
- "127.0.0.1:${APP_PORT:-42776}:42776"
|
||||
|
||||
# Two-tier networking. `web` is the app's public-facing bridge (reached via the
|
||||
# published loopback port above; it also provides egress, e.g. to a host
|
||||
# Postgres via host.docker.internal). `backend` is the private tier the app
|
||||
# uses to reach the bundled DB. The DB sits only on `backend`, so nothing on
|
||||
# the host-facing side can reach it.
|
||||
networks:
|
||||
- web
|
||||
- backend
|
||||
|
||||
# Wait for the bundled DB when the with-db profile is active. When using a
|
||||
# host Postgres the db service is disabled, and required:false keeps this
|
||||
@@ -76,6 +89,10 @@ services:
|
||||
# the app at a Postgres running on the host instead.
|
||||
profiles: ["with-db"]
|
||||
|
||||
# Private back-end tier only — never on `web`, never published.
|
||||
networks:
|
||||
- backend
|
||||
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-tanabata}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-tanabata}
|
||||
@@ -97,6 +114,21 @@ services:
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
networks:
|
||||
# Public-facing bridge for this app. The explicit bridge name (instead of
|
||||
# Docker's random br-<hash>) makes it identifiable on the host for tcpdump and
|
||||
# firewall rules.
|
||||
web:
|
||||
driver_opts:
|
||||
com.docker.network.bridge.name: dk-tanabata
|
||||
# Private back-end tier (app ↔ DB). internal:true drops the gateway so the DB
|
||||
# has no route off-host. Note: Linux caps interface names at 15 chars, and
|
||||
# dk-tanabata-bnd is exactly 15 — a longer app name would need a shorter suffix.
|
||||
backend:
|
||||
internal: true
|
||||
driver_opts:
|
||||
com.docker.network.bridge.name: dk-tanabata-bnd
|
||||
|
||||
volumes:
|
||||
app_files:
|
||||
app_thumbs:
|
||||
|
||||
Reference in New Issue
Block a user