The target is a VM inside the company network, reachable only from there. Two consequences decide whether this works at all, and both are easy to discover too late — after the firewall rules are already written. The server needs outbound access even though nothing comes in. Auth.js exchanges the authorisation code for a token server-side and fetches the issuer's configuration, so login.microsoftonline.com must be reachable from the VM; the database likewise. That the person signs in through their own browser is not enough, which is the assumption worth naming before someone builds a closed network around it. HTTPS is not optional either: Entra accepts http only for localhost. The practical route without public reachability is a public DNS name pointing at a private address and a certificate obtained through the DNS challenge — allowed, common, and it yields a normally trusted certificate while the server stays unreachable from outside. deploy/Caddyfile does that, and the alternative (self-signed, trusted on every workstation) is written down with its cost. docker-compose now publishes port 3000 on 127.0.0.1 only. It was on every interface, so the same service also stood there unencrypted, and one gap in the firewall was enough. The proxy is the only way in. AUTH_URL is documented for the same reason a comment sits in the Caddyfile: behind a proxy the container does not see the name the browser used, and the callback would point somewhere nobody can reach. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
34 lines
1.2 KiB
YAML
34 lines
1.2 KiB
YAML
services:
|
|
app:
|
|
# Ohne Build-Argumente: alles, was die Anwendung braucht — DATABASE_URL,
|
|
# AUTH_* — liest sie zur Laufzeit aus .env. Das Abbild ist damit für jede
|
|
# Umgebung dasselbe.
|
|
build:
|
|
context: .
|
|
restart: unless-stopped
|
|
# Nur auf der Loopback-Adresse, nicht auf allen Schnittstellen. Erreichbar
|
|
# ist die App damit ausschliesslich über den Reverse Proxy, der TLS
|
|
# beendet — sonst stünde daneben derselbe Dienst unverschlüsselt offen,
|
|
# und ein Fehler in der Firewall genügte.
|
|
ports:
|
|
- "127.0.0.1:3000:3000"
|
|
env_file:
|
|
- .env
|
|
|
|
# Replaces the Vercel Cron job from vercel.json (not available outside
|
|
# Vercel): calls the same endpoint on the same daily schedule using the
|
|
# same bearer-secret auth the route already expects.
|
|
cron:
|
|
image: alpine:3.20
|
|
restart: unless-stopped
|
|
depends_on:
|
|
app:
|
|
condition: service_healthy
|
|
env_file:
|
|
- .env
|
|
entrypoint: ["/bin/sh", "-c"]
|
|
command:
|
|
- |
|
|
echo "0 3 * * * /bin/sh -c 'wget -q -O- --header=\"Authorization: Bearer \$$CRON_SECRET\" http://app:3000/api/cron/apply-pending-changes >> /var/log/cron.log 2>&1'" > /etc/crontabs/root
|
|
crond -f -d 8
|