Charger control needs TLS, and the stack speaks plain HTTP, so the README said "terminate TLS in a reverse proxy" and left the operator to work out which four settings have to agree. This adds the proxy: a Caddy overlay that fronts the Web App BFF — which already carries /api/ and /ocpp/ — so browsers and chargers arrive at the same name and the certificate is issued on first boot. The four settings are derived from DV_DOMAIN, since one value getting typed right is better odds than four: OCPP_PUBLIC_URL, OCPP_REQUIRE_TLS back on, CORS, and TRUST_FORWARDED_PROTO on the Web App. That last one is the non-obvious one — without it the BFF overwrites Caddy's X-Forwarded-Proto with its own plaintext hop and the API Server rejects the charger it just told to connect over wss. The README's OCPP section was stale besides: it still sent chargers to the API Server port alone, from before the BFF carried that path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
62 lines
2.3 KiB
YAML
62 lines
2.3 KiB
YAML
name: drivervault
|
|
|
|
# TLS overlay — put a public hostname and a real certificate in front of the
|
|
# stack. Layer it on top of either base file:
|
|
#
|
|
# docker compose -f docker-compose.prod.yml -f docker-compose.tls.yml up -d
|
|
#
|
|
# What it changes:
|
|
# • Caddy terminates TLS on :443 and proxies everything to the Web App BFF,
|
|
# which already carries /api/ and /ocpp/ through to the API Server. One
|
|
# hostname serves browsers and chargers alike.
|
|
# • The charger endpoint the panel hands out becomes wss://$DV_DOMAIN/ocpp/…,
|
|
# so OCPP_REQUIRE_TLS goes back on and the control token stops crossing the
|
|
# network in the clear.
|
|
#
|
|
# Requirements, none of which this file can arrange for you:
|
|
# • DV_DOMAIN resolves to this host from the public internet, and ports 80 and
|
|
# 443 reach it (Caddy needs :80 for the ACME challenge, and keeps it for the
|
|
# redirect afterwards).
|
|
# • The charger can resolve DV_DOMAIN too. On a LAN behind NAT that usually
|
|
# means hairpin NAT or a split-DNS entry pointing the name at 10.2.1.10 —
|
|
# otherwise the charger looks up a public address it cannot route to.
|
|
|
|
services:
|
|
caddy:
|
|
image: "${CADDY_IMAGE:-caddy:2-alpine}"
|
|
container_name: drivervault-caddy
|
|
restart: unless-stopped
|
|
depends_on:
|
|
web-app:
|
|
condition: service_healthy
|
|
environment:
|
|
DV_DOMAIN: "${DV_DOMAIN:?set DV_DOMAIN in .env}"
|
|
# Let's Encrypt sends expiry warnings here if renewal ever stops working.
|
|
DV_ACME_EMAIL: "${DV_ACME_EMAIL:?set DV_ACME_EMAIL in .env}"
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
# Certificates live here. Keep the volume: wiping it re-issues on next
|
|
# boot and Let's Encrypt rate-limits that.
|
|
- caddy_data:/data
|
|
- caddy_config:/config
|
|
|
|
api-server:
|
|
environment:
|
|
# Derived from the one hostname, so there is a single thing to set.
|
|
OCPP_REQUIRE_TLS: "true"
|
|
OCPP_PUBLIC_URL: "wss://${DV_DOMAIN}"
|
|
CORS_ALLOW_ORIGINS: "https://${DV_DOMAIN}"
|
|
|
|
web-app:
|
|
environment:
|
|
# Caddy terminates TLS and says so in X-Forwarded-Proto. Believe it —
|
|
# that header is now set by the proxy in front, not by whoever dialed in.
|
|
TRUST_FORWARDED_PROTO: "true"
|
|
|
|
volumes:
|
|
caddy_data:
|
|
caddy_config:
|