Files
termanager2/docker-compose.yml
francesco afaaeb5927 Semplifica il deploy: un solo docker-compose senza anchor YAML
I due file compose andavano tenuti allineati a mano, e gli anchor
(x-app-env, <<:) sono sintassi che va conosciuta per riuscire a leggere il
file. Per un deploy che deve restare semplice era complessita' non
giustificata.

Ora c'e' un solo docker-compose.yml, esplicito, che copre produzione e
sviluppo:

- ogni servizio dichiara quello che usa, senza merge key
- mailpit e' dietro il profilo "dev", quindi non parte mai in produzione
- la porta e' legata a 127.0.0.1 per default: in produzione il traffico
  arriva da Traefik sulla rete interna, in locale basta localhost:8080.
  Per esporla davvero: APP_PORT=0.0.0.0:8080
- .env diventa l'unica fonte di configurazione, senza valori ridichiarati

Compromesso accettato: sparendo i valori fissati nel compose, DB_HOST e
REDIS_HOST devono contenere i nomi dei servizi (mariadb, redis) e non un
indirizzo. E' segnalato in .env.example, nel README e in HANDOFF.md.

Per le modifiche al codice senza ricostruire l'immagine il README documenta
un docker-compose.override.yml, che Compose applica da solo ed e' gia'
ignorato da git.

Verificato: sei servizi healthy, login e pagine a 200, redeploy idempotente,
porta esposta solo su localhost, mailpit assente senza il profilo dev.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 17:41:48 +02:00

188 lines
5.7 KiB
YAML

# =============================================================================
# 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
restart: unless-stopped
env_file: .env
environment:
# I sorgenti non cambiano dentro un'immagine gia' costruita.
PHP_OPCACHE_VALIDATE_TIMESTAMPS: 0
ports:
# Di default ascolta solo su localhost: in produzione il traffico passa
# da Traefik sulla rete interna, non da questa porta. In locale invece
# apri http://localhost:8080.
# Per esporlo davvero: APP_PORT=0.0.0.0:8080 nel .env
- "${APP_PORT:-127.0.0.1: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
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
# 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
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
# --- 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
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 e cache: meglio sfrattare chiavi che esaurire memoria.
- --maxmemory-policy
- allkeys-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
# --- 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_UI_PORT:-127.0.0.1:8025}:8025"
networks:
- internal
volumes:
db_data:
redis_data:
storage_data:
networks:
# Database e Redis restano qui, senza rotta verso l'esterno.
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