services: db: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} ports: - "${POSTGRES_PORT}:5432" volumes: - scouthub_db_data:/var/lib/postgresql/data # Crea anche i DB scouthub / scouthub_magazzino / scouthub_eventi # usati dagli altri backend, sulla stessa istanza (solo al primo init). - ./db-init:/docker-entrypoint-initdb.d:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 5s timeout: 5s retries: 10 attivita-backend: build: context: ./scouthub-attivita-be restart: unless-stopped environment: PORT: ${ATTIVITA_BACKEND_PORT} CORS_ORIGIN: ${ATTIVITA_FE_PUBLIC_URL} DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/scouthub?schema=public KEYCLOAK_BASE_URL: http://keycloak:8080 KEYCLOAK_REALM: scouthub ports: - "${ATTIVITA_BACKEND_PORT}:${ATTIVITA_BACKEND_PORT}" depends_on: db: condition: service_healthy keycloak: condition: service_started attivita-frontend: build: context: ./scouthub-attivita-fe args: API_URL: ${ATTIVITA_BE_PUBLIC_URL} KEYCLOAK_BASE_URL: ${KEYCLOAK_PUBLIC_URL} restart: unless-stopped ports: - "${ATTIVITA_FRONTEND_PORT}:80" depends_on: - attivita-backend home-backend: build: context: ./scouthub-home-be restart: unless-stopped environment: PORT: ${HOME_BACKEND_PORT} DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public FRONTEND_BASE_URL: ${HOME_FE_PUBLIC_URL} # Rete interna Docker: la validazione del JWT (JWKS) non controlla l'issuer, # quindi non serve che coincida con l'hostname visto dal browser (localhost:8081). KEYCLOAK_BASE_URL: http://keycloak:8080 KEYCLOAK_REALM: scouthub KEYCLOAK_ORG_SERVICE_CLIENT_ID: scouthub-home-be KEYCLOAK_ORG_SERVICE_CLIENT_SECRET: ${KEYCLOAK_ORG_SERVICE_CLIENT_SECRET} ports: - "${HOME_BACKEND_PORT}:${HOME_BACKEND_PORT}" depends_on: db: condition: service_healthy keycloak: condition: service_started home-frontend: build: context: ./scouthub-home-fe args: KEYCLOAK_BASE_URL: ${KEYCLOAK_PUBLIC_URL} ORG_SERVICE_API_BASE_URL: ${HOME_BE_PUBLIC_URL} ATTIVITA_FE_BASE_URL: ${ATTIVITA_FE_PUBLIC_URL} MAGAZZINO_FE_BASE_URL: ${MAGAZZINO_FE_PUBLIC_URL} EVENTI_FE_BASE_URL: ${EVENTI_FE_PUBLIC_URL} restart: unless-stopped ports: - "${HOME_FRONTEND_PORT}:80" depends_on: - home-backend magazzino-backend: build: context: ./scouthub-magazzino-be restart: unless-stopped environment: PORT: ${MAGAZZINO_BACKEND_PORT} DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/scouthub_magazzino?schema=public KEYCLOAK_BASE_URL: http://keycloak:8080 KEYCLOAK_REALM: scouthub KEYCLOAK_MAGAZZINO_CLIENT_ID: scouthub-magazzino-be KEYCLOAK_MAGAZZINO_CLIENT_SECRET: ${KEYCLOAK_MAGAZZINO_CLIENT_SECRET} ports: - "${MAGAZZINO_BACKEND_PORT}:${MAGAZZINO_BACKEND_PORT}" depends_on: db: condition: service_healthy keycloak: condition: service_started magazzino-frontend: build: context: ./scouthub-magazzino-fe args: KEYCLOAK_BASE_URL: ${KEYCLOAK_PUBLIC_URL} MAGAZZINO_API_BASE_URL: ${MAGAZZINO_BE_PUBLIC_URL} restart: unless-stopped ports: - "${MAGAZZINO_FRONTEND_PORT}:80" depends_on: - magazzino-backend eventi-backend: build: context: ./scouthub-eventi-be restart: unless-stopped environment: PORT: ${EVENTI_BACKEND_PORT} DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/scouthub_eventi?schema=public KEYCLOAK_BASE_URL: http://keycloak:8080 KEYCLOAK_REALM: scouthub KEYCLOAK_EVENTI_CLIENT_ID: scouthub-eventi-be KEYCLOAK_EVENTI_CLIENT_SECRET: ${KEYCLOAK_EVENTI_CLIENT_SECRET} KEYCLOAK_AUTHORIZED_SERVICE_CLIENTS: scouthub-eventi-be ports: - "${EVENTI_BACKEND_PORT}:${EVENTI_BACKEND_PORT}" depends_on: db: condition: service_healthy keycloak: condition: service_started eventi-frontend: build: context: ./scouthub-eventi-fe args: KEYCLOAK_BASE_URL: ${KEYCLOAK_PUBLIC_URL} EVENTI_API_BASE_URL: ${EVENTI_BE_PUBLIC_URL} restart: unless-stopped ports: - "${EVENTI_FRONTEND_PORT}:80" depends_on: - eventi-backend keycloak-db: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: keycloak POSTGRES_USER: keycloak POSTGRES_PASSWORD: ${KEYCLOAK_DB_PASSWORD} volumes: - scouthub_keycloak_db_data:/var/lib/postgresql/data keycloak-realm-render: image: alpine:3.20 restart: "no" entrypoint: ["sh", "/render-realm-template.sh"] environment: HOME_FE_URL: ${HOME_FE_PUBLIC_URL} ATTIVITA_FE_URL: ${ATTIVITA_FE_PUBLIC_URL} MAGAZZINO_FE_URL: ${MAGAZZINO_FE_PUBLIC_URL} EVENTI_FE_URL: ${EVENTI_FE_PUBLIC_URL} volumes: - ./keycloak/realm-export.template.json:/template/realm-export.template.json:ro - ./keycloak/render-realm-template.sh:/render-realm-template.sh:ro - scouthub_keycloak_realm:/rendered keycloak: image: quay.io/keycloak/keycloak:26.7 restart: unless-stopped # --import-realm importa il realm-export.json generato da keycloak-realm-render (a partire # dal template) solo se il realm non esiste già. Le correzioni post-import (ruoli service # account, protocol mapper "organization") non coperte dal file JSON sono automatizzate dal # servizio one-shot "keycloak-init" sotto, non più manuali (vedi CLAUDE.md). command: start-dev --import-realm --health-enabled=true environment: KC_DB: postgres KC_DB_URL: jdbc:postgresql://keycloak-db:5432/keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: ${KEYCLOAK_DB_PASSWORD} KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} # admin-fine-grained-authz:v1 serve per i permessi granulari sulle Organization (26.7+). # Verificare con `kc.sh show-config` che i flag richiesti siano invariati nella versione # effettivamente usata: i nomi dei feature flag cambiano tra minor version di Keycloak. KC_FEATURES: organization,admin-fine-grained-authz:v1 # KC_HOSTNAME come URL completo (schema+host, es. https://auth.tuodominio.it dietro Caddy # in produzione, http://localhost:6999 in locale): Keycloak lo usa per generare tutti i link # (redirect, well-known, ecc.) visti dal browser. KC_PROXY_HEADERS è necessario perché # Keycloak sta dietro un reverse proxy (Caddy) che termina il TLS e inoltra in http. KC_HOSTNAME: ${KEYCLOAK_PUBLIC_URL} KC_HTTP_ENABLED: "true" KC_PROXY_HEADERS: xforwarded ports: - "${KEYCLOAK_PORT}:8080" volumes: - scouthub_keycloak_realm:/opt/keycloak/data/import:ro - ./keycloak/themes/scouthub:/opt/keycloak/themes/scouthub:ro depends_on: keycloak-db: condition: service_started keycloak-realm-render: condition: service_completed_successfully healthcheck: test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000 && echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && grep -q '\"status\": \"UP\"' <&3"] interval: 5s timeout: 5s retries: 30 start_period: 30s keycloak-init: image: alpine:3.20 restart: "no" entrypoint: ["sh", "-c", "apk add --no-cache curl jq >/dev/null && sh /init-post-import.sh"] environment: KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD} volumes: - ./keycloak/init-post-import.sh:/init-post-import.sh:ro depends_on: keycloak: condition: service_healthy volumes: scouthub_db_data: scouthub_keycloak_db_data: scouthub_keycloak_realm: