diff --git a/docker-compose.yml b/docker-compose.yml index cf2e1ae..67059d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,36 @@ services: expose: - '5432' + # Pose le rôle applicatif à **chaque** démarrage de la pile. + # + # `/docker-entrypoint-initdb.d` ne s'exécute qu'à la toute première + # initialisation du volume : un volume créé par une tentative antérieure + # n'aurait jamais vu passer ce rôle, et l'application échouerait à + # s'authentifier sans que rien n'explique pourquoi. Le script est rejouable, + # ce service le rejoue. + # + # Les identifiants du superutilisateur restent ici : les confier au conteneur + # applicatif lui donnerait de quoi contourner la row-level security, ce que + # tout ce montage cherche précisément à empêcher. + db-init: + image: postgres:16-alpine + restart: 'no' + depends_on: + db: + condition: service_healthy + environment: + PGHOST: db + PGPASSWORD: ${POSTGRES_PASSWORD:-planflow-interne} + POSTGRES_USER: ${POSTGRES_USER:-planflow} + POSTGRES_DB: ${POSTGRES_DB:-planflow} + APP_DB_USER: ${APP_DB_USER:-planflow_app} + APP_DB_PASSWORD: ${APP_DB_PASSWORD:-planflow-app-interne} + volumes: + - ./docker/init-app-role.sh:/init-app-role.sh:ro + entrypoint: ['sh', '/init-app-role.sh'] + networks: + - interne + app: build: context: . @@ -46,6 +76,8 @@ services: depends_on: db: condition: service_healthy + db-init: + condition: service_completed_successfully environment: NODE_ENV: production # Le compte applicatif, **pas** le superutilisateur d'amorçage : un diff --git a/docker/init-app-role.sh b/docker/init-app-role.sh index c7ec82f..4a8210b 100755 --- a/docker/init-app-role.sh +++ b/docker/init-app-role.sh @@ -14,25 +14,57 @@ set -eu # ni superutilisateur ni BYPASSRLS. C'est précisément pourquoi les politiques # sont déclarées en FORCE : elles s'appliquent aussi au propriétaire. # -# Ce script ne s'exécute qu'à la **première** initialisation du volume. Pour une -# installation déjà en place, jouer le même SQL à la main (voir README). +# Ce script est **rejouable**, et il le doit : `/docker-entrypoint-initdb.d` ne +# s'exécute qu'à la toute première initialisation du volume. Une installation +# déjà en place — un volume créé par une tentative antérieure, par exemple — +# n'aurait jamais vu passer ce rôle, et l'application échouerait à se connecter +# sans que rien n'explique pourquoi. Il est donc aussi joué à chaque démarrage +# de la pile, par le service `db-init`. APP_ROLE="${APP_DB_USER:-planflow_app}" APP_PASSWORD="${APP_DB_PASSWORD:-planflow-app-interne}" +DB_NAME="${POSTGRES_DB:-planflow}" +DB_USER="${POSTGRES_USER:-planflow}" -psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <