The transport log settles it: the device reaches the server over https and validates the Let's Encrypt chain without trouble. The dedicated port, the Traefik entrypoint and the plain-HTTP router were all built on a hypothesis the evidence has since refused. DEVICE_ALLOW_HTTP goes back to false, so the device routes refuse an unencrypted request again. DEPLOY.md is rewritten around the real cause — the firmware does not follow redirects, and a trailing slash was answered with a 308 — and records the three hypotheses that were wrong, so nobody spends another evening on them. It also names the trap that made this slow: Traefik, a production Next server and tcpdump were each read as saying "no traffic" when all three were simply silent by default. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd
86 lines
3.2 KiB
YAML
86 lines
3.2 KiB
YAML
# Production overlay: the application is published by an existing Traefik
|
|
# rather than on a host port.
|
|
#
|
|
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build --wait
|
|
#
|
|
# Traefik must already be running and own the external network named by
|
|
# TRAEFIK_NETWORK. Nothing here holds a secret: every value comes from .env,
|
|
# which is never committed.
|
|
#
|
|
# Using Nginx Proxy Manager instead? Delete the labels and the `edge` network,
|
|
# keep the published port from docker-compose.yml bound to 127.0.0.1, and point
|
|
# a proxy host at it. The forwarded headers matter either way: the application
|
|
# builds the image URL handed to the panel from them, so X-Forwarded-Proto and
|
|
# X-Forwarded-Host must both reach it or the device will be sent to the wrong
|
|
# scheme.
|
|
|
|
services:
|
|
db:
|
|
# The database is reached only over the compose network.
|
|
ports: !override []
|
|
|
|
app:
|
|
ports: !override []
|
|
networks:
|
|
- default
|
|
- edge
|
|
# The database is the only thing worth persisting; the application writes
|
|
# nothing to its own filesystem.
|
|
read_only: true
|
|
tmpfs:
|
|
- /tmp
|
|
labels:
|
|
traefik.enable: "true"
|
|
traefik.docker.network: ${TRAEFIK_NETWORK:-web}
|
|
traefik.http.routers.horaires.rule: Host(`${APP_DOMAIN:?Set APP_DOMAIN in .env}`)
|
|
traefik.http.routers.horaires.entrypoints: ${TRAEFIK_ENTRYPOINT:-websecure}
|
|
traefik.http.routers.horaires.tls: "true"
|
|
traefik.http.routers.horaires.tls.certresolver: ${TRAEFIK_CERTRESOLVER:-myresolver}
|
|
traefik.http.routers.horaires.middlewares: horaires-hsts
|
|
traefik.http.services.horaires.loadbalancer.server.port: "3010"
|
|
# Everything is served over TLS, the panel included: its firmware
|
|
# validates the chain without trouble. A plain-HTTP route existed here
|
|
# for a while, on the belief that it could not — see DEPLOY.md.
|
|
traefik.http.middlewares.horaires-hsts.headers.stsSeconds: "31536000"
|
|
traefik.http.middlewares.horaires-hsts.headers.stsIncludeSubdomains: "true"
|
|
|
|
|
|
backup:
|
|
# A nightly dump kept for two weeks. Small, boring, and the only thing
|
|
# standing between a bad migration and retyping a year of opening hours.
|
|
image: postgres:16-alpine
|
|
restart: unless-stopped
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
environment:
|
|
PGPASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
|
|
POSTGRES_USER: ${POSTGRES_USER:-horaires}
|
|
POSTGRES_DB: ${POSTGRES_DB:-horaires}
|
|
BACKUP_KEEP_DAYS: ${BACKUP_KEEP_DAYS:-14}
|
|
volumes:
|
|
- ./backups:/backups
|
|
entrypoint:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
while true; do
|
|
stamp="$$(date +%Y%m%d-%H%M%S)"
|
|
if pg_dump -h db -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" \
|
|
| gzip > "/backups/horaires-$$stamp.sql.gz"; then
|
|
echo "[backup] /backups/horaires-$$stamp.sql.gz"
|
|
else
|
|
echo "[backup] échec du dump $$stamp" >&2
|
|
rm -f "/backups/horaires-$$stamp.sql.gz"
|
|
fi
|
|
find /backups -name 'horaires-*.sql.gz' -mtime "+$$BACKUP_KEEP_DAYS" -delete
|
|
sleep 86400
|
|
done
|
|
|
|
networks:
|
|
edge:
|
|
external: true
|
|
name: ${TRAEFIK_NETWORK:-web}
|