Files
ita-ito-horaires/docker-compose.prod.yml
vliaudatandClaude Opus 5 13abcf3242 chore: drop the plain-HTTP route now the panel is proven on TLS
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
2026-09-21 23:19:45 +02:00

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}