Files
termanager2/docker-compose.yml

239 lines
8.1 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:
# 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
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
# --- 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 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
# --- 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
# 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