Files
parking_solution/docker-compose.prod.yml
T
julian c87dcb2253 fix(deploy): server on host network so it sees the booth LAN / device VLAN
In prod the containerized server sat on the Docker bridge (172.18.0.x) and could
only see eth0 — so the setup backend-IP picker (net.ts networkInterfaces) showed
just the Docker IP, the server couldn't reach the relay or fetch Hikvision ISAPI
snapshots, and push devices (readers/cameras) couldn't reach it. The server is the
ONLY container doing device I/O, so put it on the HOST network namespace.

- docker-compose.prod.yml: server + proxy → network_mode: host (server detaches the
  base `parking` network via `networks: !reset []`). server VISION_URL=127.0.0.1:8089.
  vision stays BRIDGED (it never touches a device — the server hands it JPEG bytes)
  but publishes 8089 on 127.0.0.1 only, so the host-net server reaches it over
  loopback while the ANPR service stays off the LAN.
- docker-compose.yml: VISION_URL is now ${VISION_URL:-http://vision:8089} so dev keeps
  compose-DNS service-name routing; prod overrides to loopback.
- Caddyfile: reverse_proxy 127.0.0.1:3000 (was server:3000 — service DNS doesn't
  resolve on host net). Dev doesn't use Caddy, so unaffected.

Merge validated for both envs (booth.sh config, exit 0). Host-net side effect: the
container ping_group_range sysctl is a no-op — the HOST must set it for reader ICMP
liveness (see appliance-provisioning).

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-27 12:48:27 +02:00

80 lines
3.6 KiB
YAML

# PROD override: pull pinned registry images (no local build), restart always, real
# recognizer, and a CADDY reverse proxy in front so operators reach the booth on a clean
# port-80 URL (no :3000) — and a path to real TLS later. Server + vision stay INTERNAL
# (only Caddy publishes a port). Use with the base file and pin TAG to the branch you deploy:
# REGISTRY=git.infra.msai.al/mca/parking_solution TAG=main \
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# See wiki/decisions/container-deployment.md.
services:
# Reverse proxy: :80 → server (127.0.0.1:3000). On the HOST network (see the server note),
# so it reaches the host-net server over loopback and publishes :80 directly on the host.
# WebSocket /api/ws upgrades pass through natively. Swapping http:// for the site's real
# hostname later enables automatic HTTPS. Reached at http://<name-or-ip>/ (name via hosts/DNS
# on-site — NOT baked into any image).
proxy:
image: caddy:2-alpine
restart: always
# Host network: Caddy listens on the host's :80 and proxies the host-net server on
# 127.0.0.1:3000. (No `ports:` mapping — host mode publishes directly.)
network_mode: host
# host mode is mutually exclusive with a named network; the base file doesn't attach proxy,
# so nothing to null here (server does — see below).
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
depends_on:
- server
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
server:
restart: always
# HOST NETWORK — the crux of the appliance. The server is the ONLY container doing device
# I/O (camera ISAPI snapshots, relay control, receiving reader/alarm pushes), all on the
# booth's LAN / isolated device VLAN (10.0.10.x). On a bridge network it sees only the Docker
# subnet (172.18.0.x) — it can't reach the relay, can't be reached by push devices, and the
# backend-IP picker (net.ts networkInterfaces) only sees eth0. Host mode puts it on the real
# NICs. Vision stays bridged (it never touches a device — the server hands it JPEG bytes).
network_mode: host
# host mode is mutually exclusive with a named network — detach the base file's `parking`
# attachment (compose errors otherwise: "network_mode and networks cannot both be set").
networks: !reset []
# Listens on :3000 directly on the host (Caddy proxies it). Loopback to vision:
environment:
VISION_URL: http://127.0.0.1:8089
# Reader liveness ping (Dingtian/GEE QR push-only readers): on host net the server uses the
# HOST's net.ipv4.ping_group_range — ensure the host sets it (see appliance-provisioning).
# The container-level sysctl below is a no-op under host net but kept as documentation.
sysctls:
- net.ipv4.ping_group_range=0 2147483647
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
vision:
restart: always
# The real ANPR engine. The image baked the model weights at build (offline-first).
# Stays on the bridge network (isolated — it makes NO outbound device calls), but PUBLISHES
# 8089 on the host LOOPBACK ONLY so the host-net server can reach it. 127.0.0.1 binding keeps
# it off the booth LAN — nothing on the network can hit the ANPR service.
environment:
VISION_RECOGNIZER: fast_alpr
ports:
- "127.0.0.1:8089:8089"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
caddy-data:
caddy-config: