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>
This commit is contained in:
@@ -1,60 +1,37 @@
|
||||
# =============================================================================
|
||||
# TerManager2 — production stack (Dokploy)
|
||||
# TerManager2
|
||||
#
|
||||
# The application image is self-contained: PHP dependencies and compiled
|
||||
# frontend assets are baked in at build time, and nginx runs alongside php-fpm
|
||||
# inside the app container. There are no bind mounts and no build steps at
|
||||
# boot, so a redeploy is: build image, migrate, serve.
|
||||
# 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).
|
||||
#
|
||||
# Configuration comes from the .env file, which Dokploy writes from the
|
||||
# variables you set in its UI. This file does not re-declare them — it only
|
||||
# pins the handful of values dictated by the topology below (service names,
|
||||
# which datastores exist), because those must match this file, not .env.
|
||||
# Produzione (Dokploy): deploy dall'interfaccia, niente da fare qui
|
||||
# Locale: docker compose --profile dev up -d --build
|
||||
#
|
||||
# For local development use docker-compose.dev.yml instead.
|
||||
# L'immagine e' autosufficiente: dipendenze PHP e asset frontend sono compilati
|
||||
# durante il build, non all'avvio.
|
||||
# =============================================================================
|
||||
|
||||
x-app: &app
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/php/Dockerfile
|
||||
restart: unless-stopped
|
||||
# required:false so `docker compose config` and a bare checkout still work;
|
||||
# a missing .env then surfaces as the explicit DB_PASSWORD error below.
|
||||
env_file:
|
||||
- path: .env
|
||||
required: false
|
||||
volumes:
|
||||
# Uploaded territory PDFs, thumbnails, logs and the persisted APP_KEY.
|
||||
- storage_data:/var/www/html/storage
|
||||
networks:
|
||||
- internal
|
||||
|
||||
# Values that are a property of THIS compose file rather than of the
|
||||
# deployment, so they override whatever .env happens to say. A DB_HOST of
|
||||
# 127.0.0.1 left over from a local .env would otherwise break the container.
|
||||
x-app-env: &app-env
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: mariadb
|
||||
DB_PORT: 3306
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
# The stack ships Redis and the worker below consumes the redis queue;
|
||||
# pointing these elsewhere would silently break job processing.
|
||||
CACHE_STORE: redis
|
||||
SESSION_DRIVER: redis
|
||||
QUEUE_CONNECTION: redis
|
||||
# Logs go to the container's stderr, where `docker logs` and the Dokploy
|
||||
# log viewer can see them, rather than into a file inside the volume.
|
||||
LOG_CHANNEL: stderr
|
||||
|
||||
services:
|
||||
|
||||
# --- Applicazione (nginx + php-fpm) ---------------------------------------
|
||||
app:
|
||||
<<: *app
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/php/Dockerfile
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
<<: *app-env
|
||||
# Sources never change in a built image, so skip the stat() on every hit.
|
||||
# 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
|
||||
@@ -64,18 +41,26 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
# --- Job in coda (import PDF massivi) -------------------------------------
|
||||
queue-worker:
|
||||
<<: *app
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/php/Dockerfile
|
||||
restart: unless-stopped
|
||||
user: www-data
|
||||
environment:
|
||||
<<: *app-env
|
||||
PHP_OPCACHE_VALIDATE_TIMESTAMPS: 0
|
||||
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 recycles the worker hourly so a leaked handle or grown heap
|
||||
# can't accumulate indefinitely; restarting it is Docker's job.
|
||||
# --max-time ricicla il worker ogni ora, cosi' un handle non rilasciato o
|
||||
# la memoria cresciuta non si accumulano indefinitamente.
|
||||
command:
|
||||
- php
|
||||
- artisan
|
||||
@@ -86,7 +71,7 @@ services:
|
||||
- --tries=3
|
||||
- --timeout=600
|
||||
- --max-time=3600
|
||||
# Overrides the image's nginx-based HEALTHCHECK (no web server here).
|
||||
# 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
|
||||
@@ -94,21 +79,25 @@ services:
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
# --- Task pianificati (pulizia audit log) ---------------------------------
|
||||
scheduler:
|
||||
<<: *app
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/php/Dockerfile
|
||||
restart: unless-stopped
|
||||
user: www-data
|
||||
environment:
|
||||
<<: *app-env
|
||||
env_file: .env
|
||||
volumes:
|
||||
- storage_data:/var/www/html/storage
|
||||
networks:
|
||||
- internal
|
||||
depends_on:
|
||||
app:
|
||||
condition: service_healthy
|
||||
entrypoint: ["queue-entrypoint.sh"]
|
||||
# `schedule:work` is the container-native replacement for a host crontab —
|
||||
# it wakes every minute and dispatches whatever routes/console.php defines
|
||||
# (currently the audit-log cleanup).
|
||||
# Sostituisce il crontab di sistema: si sveglia ogni minuto ed esegue
|
||||
# quanto definito in routes/console.php.
|
||||
command: ["php", "artisan", "schedule:work"]
|
||||
# Same reason as above: this container runs neither nginx nor php-fpm, so
|
||||
# the image's HTTP health check would mark it unhealthy forever.
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pgrep -f 'schedule:work' > /dev/null || exit 1"]
|
||||
interval: 30s
|
||||
@@ -116,18 +105,17 @@ services:
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
# --- Database -------------------------------------------------------------
|
||||
mariadb:
|
||||
image: mariadb:11
|
||||
restart: unless-stopped
|
||||
# Interpolated (not env_file) because these are the *image's* own bootstrap
|
||||
# variables under different names. The `:?` guards are what make a deploy
|
||||
# with no configured passwords fail loudly instead of coming up with
|
||||
# whatever default happened to be lying around.
|
||||
# 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:?DB_PASSWORD is required}
|
||||
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?DB_ROOT_PASSWORD is required}
|
||||
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
|
||||
@@ -144,20 +132,21 @@ services:
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
# --- Cache, sessioni, code ------------------------------------------------
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD:?manca REDIS_PASSWORD nel .env}
|
||||
command:
|
||||
- redis-server
|
||||
- --appendonly
|
||||
- "yes"
|
||||
- --requirepass
|
||||
- ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
|
||||
- ${REDIS_PASSWORD:?manca REDIS_PASSWORD nel .env}
|
||||
- --maxmemory
|
||||
- 256mb
|
||||
# Sessions and cache live here; evict volatile keys rather than OOM.
|
||||
# Qui vivono sessioni e cache: meglio sfrattare chiavi che esaurire memoria.
|
||||
- --maxmemory-policy
|
||||
- allkeys-lru
|
||||
volumes:
|
||||
@@ -171,16 +160,28 @@ services:
|
||||
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:
|
||||
# Datastores stay on an isolated network with no route to the outside.
|
||||
# Database e Redis restano qui, senza rotta verso l'esterno.
|
||||
internal:
|
||||
driver: bridge
|
||||
# Dokploy's shared Traefik network. Only `app` joins it, and Dokploy attaches
|
||||
# the routing labels when you assign a domain to that service in the UI.
|
||||
# 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
|
||||
|
||||
Reference in New Issue
Block a user