services: app: # Image publiée, épinglée par DIGEST sur la version de ce dépôt : un # `docker compose pull` tire exactement l'image de la release, jamais une # image poussée depuis. Pour mettre à jour, récupère la nouvelle version du # dépôt (`git pull`) : ce fichier y porte le digest suivant. # # Installation depuis les sources : `build:` vit dans docker-compose.build.yml. # Docker refuse de taguer un build local avec une référence qui contient un # digest (« build tag cannot contain a digest ») — les deux ne peuvent pas # cohabiter dans ce fichier. image: ghcr.io/physalis-cloud/physalis:1.7.1@sha256:2af2222d45560a6ba7034db97e007ee8e96ab4248a170533ab109f7d0eebce72 container_name: physalis restart: unless-stopped env_file: .env environment: DATABASE_URL: postgresql://physalis:${DB_PASSWORD}@db:5432/physalis PORT: "3000" ports: # ⚠️ Défaut volontairement FERMÉ. Sans adresse, Docker publie sur 0.0.0.0 # ET contourne le pare-feu de l'hôte (la chaîne DOCKER-USER est vide par # défaut) : un gestionnaire de secrets devient joignable depuis l'Internet # public dès que l'IP du serveur est connue — et une IP se découvre # (historique DNS, journaux de transparence des certificats). # # Sur ta machine, 127.0.0.1 suffit : http://localhost:3000 fonctionne. # Sur un serveur distant, mets Physalis derrière un reverse proxy (nginx, # Caddy, Traefik) qui termine le TLS et parle au conteneur en local. # Si tu dois vraiment publier le port, pose `BIND_IP` dans `.env` : # l'adresse d'une interface privée (VPN, réseau interne) de préférence, # `0.0.0.0` en dernier recours et seulement derrière un pare-feu. - "${BIND_IP:-127.0.0.1}:${PORT:-3000}:3000" security_opt: - no-new-privileges:true read_only: true # Aucune capability n'est nécessaire : le process écoute sur 3000 (> 1024, # donc pas de NET_BIND_SERVICE) et tourne en `nextjs` uid 1001, pas en root. # À NE PAS reporter sur `db` : l'entrypoint postgres démarre en root et a # besoin de SETUID/SETGID/CHOWN pour redescendre sur l'utilisateur postgres. cap_drop: - ALL tmpfs: - /tmp volumes: # Next.js écrit le cache d'images optimisées ici, et le rootfs est en # lecture seule → un tmpfs est obligatoire. Syntaxe LONGUE avec # `mode: 01777` (world-writable + sticky) : la syntaxe courte laisse le # montage non inscriptible par l'utilisateur non-root `nextjs`, d'où un # "EACCES: mkdir '/app/.next/cache/images'" au démarrage. - type: tmpfs target: /app/.next/cache tmpfs: mode: 01777 # Limites de ressources — décommente et ajuste à ta machine. Volontairement # absentes par défaut : une limite mémoire trop basse fait tuer le conteneur # par l'OOM killer sur une grosse instance, et l'inverse d'un durcissement, # c'est une indisponibilité. Ces valeurs conviennent à un usage courant. # deploy: # resources: # limits: # cpus: '1.0' # memory: 512M # pids: 100 depends_on: db: condition: service_healthy networks: - internal db: # Version précise de postgres. Une montée arrive avec une nouvelle version # de Physalis ; ne change jamais la majeure (16) à la main, c'est une # migration de données, pas un changement de tag. image: postgres:16.15-alpine container_name: physalis-db restart: always environment: POSTGRES_USER: physalis POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: physalis volumes: - pg_data:/var/lib/postgresql/data networks: - internal healthcheck: test: ["CMD-SHELL", "pg_isready -U physalis"] interval: 5s retries: 10 volumes: pg_data: networks: internal: