metalfrom.eu/docker-compose.yml
Nicolas FRYDER 96ee3b831d chore(infra): en-têtes de sécurité, conteneurs non-root, retrait de pgAdmin
Les deux sites étaient servis par nginx sans aucun en-tête de sécurité : le
helmet de l'API ne couvre que les réponses JSON, pas les pages HTML et JS.
L'admin, qui déclenche des actions destructrices, reçoit une CSP stricte —
script-src 'self' est tenable, son app.js n'utilisant aucun handler inline. Le
site public reçoit une CSP plus permissive sur script/style/img, pour ne pas
casser la carte, mais verrouille object-src, base-uri et frame-ancestors.

pgAdmin est retiré des deux compose sur décision explicite : une interface
d'administration de base exposée sur Internet, pour un usage ponctuel.

- Images de base épinglées (nginx:alpine → nginx:1.27-alpine).
- Utilisateur non-root pour l'api, le crawler et le geocoder.
- Le geocoder ne copie plus que src/ au lieu de tout le contexte.
- .dockerignore ajouté au crawler : son COPY src embarquait __pycache__.
- Keepalive vers l'upstream API (proxy_http_version 1.1 + Connection "").

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:44:26 +02:00

112 lines
3.2 KiB
YAML

services:
geocoder-enqueue:
build:
context: apps/geocoder
working_dir: /app
environment:
DATABASE_URL: ${DATABASE_URL}
GEOCODE_ENQUEUE_BATCH: "100000"
GEOCODE_ENQUEUE_POLL: "15"
ENQUEUE_AUTO_INTERVAL_MIN: "60"
command: ["python", "src/enqueue.py"]
networks:
- coolify
labels:
- traefik.enable=false
restart: unless-stopped
geocoder-worker:
build:
context: apps/geocoder
working_dir: /app
environment:
DATABASE_URL: ${DATABASE_URL}
GEOAPIFY_API_KEY: ${GEOAPIFY_API_KEY}
GEOCODER_MIN_DELAY: "0.22"
GEOCODER_JITTER: "0.10"
GEOCODE_MAX_PER_RUN: "100000"
GEOCODE_MAX_GEO_TRIES: "3"
GEOCODE_MIN_CONFIDENCE: "0.7"
command: ["python", "src/worker.py"]
networks:
- coolify
labels:
- traefik.enable=false
restart: unless-stopped
groq-worker:
build:
context: apps/geocoder
working_dir: /app
environment:
DATABASE_URL: ${DATABASE_URL}
GROQ_API_KEY: ${GROQ_API_KEY}
GROQ_MAX_TRIES: "5"
GROQ_CALL_DELAY: "2.5"
command: ["python", "src/groq_worker.py"]
networks:
- coolify
labels:
- traefik.enable=false
restart: unless-stopped
api:
build:
context: apps/api
environment:
PORT: "3000"
DATABASE_URL: ${DATABASE_URL}
BM_IMPORT_TOKEN: ${BM_IMPORT_TOKEN}
CORS_ORIGINS: ${CORS_ORIGINS:-https://metalfrom.eu,https://www.metalfrom.eu}
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
ADMIN_SEED_USERNAME: ${ADMIN_SEED_USERNAME}
ADMIN_SEED_PASSWORD_HASH: ${ADMIN_SEED_PASSWORD_HASH}
networks:
coolify:
aliases:
- bm-api-internal
labels:
- traefik.enable=true
- traefik.docker.network=coolify
- traefik.http.routers.bm-api.rule=Host(`bm.nicolasfryder.ovh`)
- traefik.http.routers.bm-api.entrypoints=https
- traefik.http.routers.bm-api.tls=true
- traefik.http.routers.bm-api.tls.certresolver=letsencrypt
- traefik.http.services.bm-api.loadbalancer.server.port=3000
admin:
build:
context: apps/admin
args:
API_UPSTREAM: http://bm-api-internal:3000
networks:
- coolify
labels:
- traefik.enable=true
- traefik.docker.network=coolify
- traefik.http.routers.bm-admin.rule=Host(`admin.metalfrom.eu`)
- traefik.http.routers.bm-admin.entrypoints=https
- traefik.http.routers.bm-admin.tls=true
- traefik.http.routers.bm-admin.tls.certresolver=letsencrypt
- traefik.http.services.bm-admin.loadbalancer.server.port=80
web:
build:
context: apps/web
networks:
- coolify
labels:
- traefik.enable=true
- traefik.docker.network=coolify
- traefik.http.routers.bm-web.rule=Host(`metalfrom.eu`) || Host(`www.metalfrom.eu`)
- traefik.http.routers.bm-web.entrypoints=https
- traefik.http.routers.bm-web.tls=true
- traefik.http.routers.bm-web.tls.certresolver=letsencrypt
- traefik.http.routers.bm-web.tls.domains[0].main=metalfrom.eu
- traefik.http.routers.bm-web.tls.domains[0].sans=www.metalfrom.eu
- traefik.http.services.bm-web.loadbalancer.server.port=80
networks:
coolify:
external: true
name: coolify