# ============================================================================= # TerManager2 # # Un solo file per produzione e sviluppo. Tutta la configurazione sta nel file # .env (su Dokploy lo genera l'interfaccia, in locale lo copi da .env.example). # # Produzione (Dokploy): deploy dall'interfaccia, niente da fare qui # Locale: docker compose --profile dev up -d --build # # L'immagine e' autosufficiente: dipendenze PHP e asset frontend sono compilati # durante il build, non all'avvio. # ============================================================================= services: # --- Applicazione (nginx + php-fpm) --------------------------------------- app: build: context: . dockerfile: docker/php/Dockerfile # Vuota = la versione fissata nel Dockerfile (ARG PHP_VERSION). Si # imposta solo per provare in locale una versione diversa. args: - PHP_VERSION restart: unless-stopped env_file: .env environment: # I sorgenti non cambiano dentro un'immagine gia' costruita. PHP_OPCACHE_VALIDATE_TIMESTAMPS: 0 ports: # Interfaccia e porta si decidono separatamente nel .env: # APP_BIND=0.0.0.0 raggiungibile dall'esterno (http://IP:8080) # APP_BIND=127.0.0.1 solo dalla macchina stessa, per quando il # traffico arriva da un reverse proxy - "${APP_BIND:-0.0.0.0}:${APP_PORT:-8080}:80" volumes: # PDF territori, miniature, log e chiave di fallback. - storage_data:/var/www/html/storage networks: - internal - dokploy-network depends_on: mariadb: condition: service_healthy redis: condition: service_healthy # --- Job in coda (import PDF massivi) ------------------------------------- queue-worker: build: context: . dockerfile: docker/php/Dockerfile # Vuota = la versione fissata nel Dockerfile (ARG PHP_VERSION). Si # imposta solo per provare in locale una versione diversa. args: - PHP_VERSION restart: unless-stopped user: www-data env_file: .env volumes: - storage_data:/var/www/html/storage networks: - internal depends_on: app: condition: service_healthy # Non esegue migrazioni: le fa solo il container app, per evitare che due # container in gara lascino uno schema applicato a meta'. entrypoint: ["queue-entrypoint.sh"] # --max-time ricicla il worker ogni ora, cosi' un handle non rilasciato o # la memoria cresciuta non si accumulano indefinitamente. command: - php - artisan - queue:work - redis - --queue=default - --sleep=3 - --tries=3 - --timeout=600 - --max-time=3600 # Al riavvio (ogni deploy) il worker riceve SIGTERM e finisce il job in # corso prima di uscire; Docker pero' di default aspetta 10 secondi e poi # lo uccide. Un ripristino o un import interrotto cosi' lascia i dati a # meta'. 16 minuti coprono il job piu' lungo (900 s). stop_grace_period: 16m # Sostituisce l'healthcheck dell'immagine, che interroga nginx: qui non c'e'. healthcheck: test: ["CMD-SHELL", "pgrep -f 'queue:work' > /dev/null || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 60s # --- Task pianificati (pulizia audit log) --------------------------------- scheduler: build: context: . dockerfile: docker/php/Dockerfile # Vuota = la versione fissata nel Dockerfile (ARG PHP_VERSION). Si # imposta solo per provare in locale una versione diversa. args: - PHP_VERSION restart: unless-stopped user: www-data env_file: .env volumes: - storage_data:/var/www/html/storage networks: - internal depends_on: app: condition: service_healthy entrypoint: ["queue-entrypoint.sh"] # Sostituisce il crontab di sistema: si sveglia ogni minuto ed esegue # quanto definito in routes/console.php. command: ["php", "artisan", "schedule:work"] healthcheck: test: ["CMD-SHELL", "pgrep -f 'schedule:work' > /dev/null || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 60s # --- Renderer di piantine (mappe OSM ad alta definizione) ----------------- # Servizio autonomo: l'app gli chiede via HTTP il rendering di un poligono # e riceve un PNG. Non monta lo storage e non tocca il database; vive solo # sulla rete interna. La cache delle tessere OSM sta in un volume dedicato. map-renderer: build: context: docker/renderer restart: unless-stopped networks: - internal volumes: - tile_cache:/tile-cache # --- Database ------------------------------------------------------------- mariadb: image: mariadb:11 restart: unless-stopped # Le ":?" fanno fallire subito il deploy se manca una password, invece di # avviare un database di produzione con credenziali di default. environment: MARIADB_DATABASE: ${DB_DATABASE:-termanager2} MARIADB_USER: ${DB_USERNAME:-termanager2} MARIADB_PASSWORD: ${DB_PASSWORD:?manca DB_PASSWORD nel .env} MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?manca DB_ROOT_PASSWORD nel .env} MARIADB_AUTO_UPGRADE: "1" command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --innodb-buffer-pool-size=256M ports: # Legata a localhost: ci arrivi con un client grafico via tunnel SSH # ssh -L 3306:127.0.0.1:3306 utente@server # senza esporre il database a Internet. Per esporlo davvero (sconsigliato) # metti DB_BIND=0.0.0.0 nel .env. # Se la 3306 e' gia' occupata sull'host, cambia DB_PORT_HOST. - "${DB_BIND:-127.0.0.1}:${DB_PORT_HOST:-3306}:3306" volumes: - db_data:/var/lib/mysql networks: - internal healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 10s timeout: 5s retries: 10 start_period: 30s # --- Cache, sessioni, code ------------------------------------------------ redis: image: redis:7-alpine restart: unless-stopped environment: REDIS_PASSWORD: ${REDIS_PASSWORD:?manca REDIS_PASSWORD nel .env} command: - redis-server - --appendonly - "yes" - --requirepass - ${REDIS_PASSWORD:?manca REDIS_PASSWORD nel .env} - --maxmemory - 256mb # Qui vivono sessioni, cache e la coda. volatile-lru sfratta solo le # chiavi con scadenza (sessioni, cache): con allkeys-lru, a memoria # piena, potevano sparire anche i job in coda, senza errori. - --maxmemory-policy - volatile-lru volumes: - redis_data:/data networks: - internal healthcheck: test: ["CMD-SHELL", "redis-cli -a \"$$REDIS_PASSWORD\" ping | grep -q PONG"] interval: 10s timeout: 5s retries: 10 start_period: 10s # --- Suite di test (solo sviluppo) ---------------------------------------- # Parte solo con --profile test, mai in produzione, e non resta in piedi: # # docker compose --profile test run --build --rm test # docker compose --profile test run --build --rm test --filter BackupArchiveTest # # Il --build serve: il sorgente NON e' montato, quindi senza ricostruzione si # eseguirebbero i test dell'ultima immagine invece di quelli sul disco. E' una # scelta di velocita', non di purezza: con il sorgente montato ogni bootstrap # di Laravel legge una decina di file attraverso il bind mount di Docker # Desktop, e la stessa suite passa da ~15 secondi a ~2 minuti. La # ricostruzione incrementale (COPY + dump-autoload) costa una ventina di # secondi e riparte dalla cache. # # Non dipende da mariadb ne' da redis: phpunit.xml impone SQLite in memoria e # cache 'array', cosi' la suite gira anche a stack spento. test: build: context: . dockerfile: docker/php/Dockerfile # Vuota = la versione fissata nel Dockerfile (ARG PHP_VERSION). Si # imposta solo per provare in locale una versione diversa. args: - PHP_VERSION # Senza questo si costruirebbe l'immagine di produzione, che essendo # installata senza dipendenze di sviluppo non contiene phpunit. target: test profiles: ["test"] # Niente env_file: la configurazione dei test sta tutta in phpunit.xml, e # un .env di sviluppo qui dentro punterebbe la suite al database vero. networks: - internal # --- Posta di test (solo sviluppo) ---------------------------------------- # Parte solo con --profile dev, mai in produzione. mailpit: image: axllent/mailpit:latest profiles: ["dev"] restart: unless-stopped ports: - "${MAILPIT_BIND:-127.0.0.1}:${MAILPIT_UI_PORT:-8025}:8025" networks: - internal volumes: db_data: redis_data: storage_data: tile_cache: networks: # Rete di servizio fra i container. Redis vive solo qui; MariaDB pubblica # anche una porta sull'host, legata a localhost salvo diversa indicazione. internal: driver: bridge # Rete condivisa del Traefik di Dokploy: e' cosi' che il dominio raggiunge # il container. In locale creala una volta con: # docker network create dokploy-network dokploy-network: external: true