# syntax=docker/dockerfile:1 # Versione di PHP, fissata alla patch. Va dichiarata prima del primo FROM per # poterla usare nel FROM dello stage `base`. # # Fissata e non "8.3": quel tag si sposta, ma Docker tiene in cache la base già # scaricata, e una ricostruzione senza --pull (anche su Dokploy) poteva uscire # con un PHP vecchio senza che nessuno se ne accorgesse. Con la patch scritta # qui, una build o prende questa versione o fallisce. # # 8.3.36: corregge CVE-2026-109618 e CVE-2026-109619 (esecuzione di codice da # remoto nel core di PHP, CVSS 9.2, rilascio del 21/10/2026). Non scendere # sotto. Aggiornala a ogni rilascio di sicurezza del ramo 8.3. # # Solo per provare in locale prima che l'immagine corretta sia su Docker Hub: # PHP_VERSION=8.3.35 docker compose ... (mai per un deploy). ARG PHP_VERSION=8.3.36 # ============================================================================= # Stage 1 — PHP dependencies (no dev, production autoloader) # ============================================================================= FROM composer:2 AS vendor WORKDIR /app # --no-scripts: artisan isn't available yet, package discovery would fail. # Autoload is optimized in the runtime stage once the full source is present. COPY composer.json composer.lock ./ RUN composer install \ --no-interaction \ --no-dev \ --prefer-dist \ --no-progress \ --no-scripts \ --ignore-platform-reqs # ============================================================================= # Stage 2 — PHP dependencies WITH dev (solo per la suite di test) # # Stesso composer.lock dello stage sopra, senza --no-dev: e' l'unico posto in # cui phpunit esiste. L'immagine di runtime non lo contiene di proposito, e non # deve iniziare a contenerlo — questo stage finisce solo nell'immagine `test`. # ============================================================================= FROM composer:2 AS vendor-dev WORKDIR /app COPY composer.json composer.lock ./ RUN composer install \ --no-interaction \ --prefer-dist \ --no-progress \ --no-scripts \ --ignore-platform-reqs # ============================================================================= # Stage 3 — Frontend assets (Vite + Tailwind) # ============================================================================= FROM node:20-alpine AS assets WORKDIR /app COPY package.json package-lock.json* ./ RUN if [ -f package-lock.json ]; then \ npm ci --no-audit --no-fund; \ else \ npm install --no-audit --no-fund; \ fi # Tailwind 4 scans blade/php sources for class names, so they must be present. COPY vite.config.js ./ COPY resources ./resources COPY app ./app COPY config ./config RUN npm run build # ============================================================================= # Stage 4 — Base di sistema (PHP + estensioni + nginx/supervisor) # # Condivisa da `runtime` e da `test`, cosi' la suite gira sulle stesse # estensioni e sullo stesso php.ini della produzione: un test che passa qui # non puo' passare grazie a un ambiente diverso da quello vero. # ============================================================================= FROM php:${PHP_VERSION}-fpm AS base # nginx + supervisor run in-image alongside php-fpm: a separate nginx container # would need its own copy of public/ plus the storage volume just to serve # static files, which is more moving parts than this app warrants. # poppler-utils provides pdftoppm/pdfinfo used by TerritorioThumbnailService. # Node is deliberately absent here: assets are built in the `assets` stage. # The -dev packages are kept rather than purged. Their runtime counterparts # are named per Debian release (libicu72 vs libicu76, libzip4 vs libzip5), so # purging means pinning package names that break whenever the php base image # moves to a new Debian. A few tens of MB is worth not having that breakage. # # phpredis si scarica da GitHub, versione fissata, e non con `pecl install`: # il 21/10/2026, giorno della patch di sicurezza di PHP, pecl.php.net # rispondeva 504 e la build falliva proprio quando bisognava aggiornare. ARG PHPREDIS_VERSION=6.3.0 RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ netcat-openbsd \ procps \ nginx \ supervisor \ poppler-utils \ libpng-dev \ libjpeg62-turbo-dev \ libfreetype6-dev \ libonig-dev \ libxml2-dev \ libzip-dev \ libicu-dev \ && docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j"$(nproc)" \ pdo_mysql \ mbstring \ exif \ pcntl \ bcmath \ gd \ intl \ zip \ opcache \ && mkdir -p /usr/src/php/ext/redis \ && curl -fsSL --retry 5 --retry-delay 5 \ "https://github.com/phpredis/phpredis/archive/refs/tags/${PHPREDIS_VERSION}.tar.gz" \ | tar xz --strip-components=1 -C /usr/src/php/ext/redis \ && echo redis >> /usr/src/php-available-exts \ && docker-php-ext-install -j"$(nproc)" redis \ && docker-php-source delete \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Kept only so `composer dump-autoload` can run below; harmless at runtime. COPY --from=composer:2 /usr/bin/composer /usr/bin/composer COPY docker/php/php.ini /usr/local/etc/php/conf.d/custom.ini COPY docker/php/www.conf /usr/local/etc/php-fpm.d/www.conf COPY docker/nginx/default.conf /etc/nginx/sites-available/default COPY docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf WORKDIR /var/www/html # ============================================================================= # Stage 5 — Immagine di test # # Uguale al runtime tranne per il vendor, che qui include phpunit. Non monta # nessun volume: la suite gira su SQLite in memoria e cache `array`, quindi non # ha bisogno ne' di MariaDB ne' di Redis (vedi phpunit.xml). # # Si usa dal compose: # docker compose --profile test run --rm test # docker compose --profile test run --rm test --filter BackupArchiveTest # ============================================================================= FROM base AS test COPY --chown=www-data:www-data . . COPY --from=vendor-dev --chown=www-data:www-data /app/vendor ./vendor RUN mkdir -p \ storage/framework/cache/data \ storage/framework/sessions \ storage/framework/views \ storage/logs \ storage/app/public \ bootstrap/cache \ && chown -R www-data:www-data storage bootstrap/cache \ && chmod -R 775 storage bootstrap/cache # --optimize non e' un vezzo: senza classmap ogni risoluzione di classe diventa # un file_exists sul sorgente, che nel container di test arriva da un bind mount # (lento su Docker Desktop, lentissimo se il progetto sta in una cartella # sincronizzata). Misurato: 74s contro 13s sulla stessa suite. Le classi # aggiunte dopo il build non finiscono nella mappa ma restano raggiungibili dal # PSR-4, quindi un test nuovo non obbliga a ricostruire l'immagine. RUN composer dump-autoload --optimize --no-interaction \ && php artisan package:discover --ansi # Sostituisce ENTRYPOINT/CMD di php:8.3-fpm: questo container non serve # richieste, esegue la suite e esce con il codice di phpunit. Gli argomenti # passati a `docker compose run test` arrivano direttamente a phpunit. ENTRYPOINT ["php", "vendor/bin/phpunit"] # ============================================================================= # Stage 6 — Runtime # # DEVE RESTARE L'ULTIMO STAGE DEL FILE. `docker build` senza --target costruisce # l'ultimo, ed e' cosi' che lo invocano sia docker-compose.yml (i tre servizi # non dichiarano `target`) sia Dokploy. Uno stage aggiunto qui sotto diventa in # silenzio l'immagine di produzione. # ============================================================================= FROM base AS runtime # Everything the app needs is baked into the image. Runtime containers do NOT # bind-mount the source, so there is no composer/npm work at boot. COPY --chown=www-data:www-data . . COPY --from=vendor --chown=www-data:www-data /app/vendor ./vendor COPY --from=assets --chown=www-data:www-data /app/public/build ./public/build # Writable paths. storage/ is replaced by a named volume at runtime; this is # the skeleton the entrypoint repopulates when that volume starts out empty. RUN mkdir -p \ storage/framework/cache/data \ storage/framework/sessions \ storage/framework/views \ storage/logs \ storage/app/public \ bootstrap/cache \ && chown -R www-data:www-data storage bootstrap/cache \ && chmod -R 775 storage bootstrap/cache RUN composer dump-autoload --optimize --no-dev --no-interaction \ && php artisan package:discover --ansi COPY docker/php/entrypoint.sh /usr/local/bin/entrypoint.sh COPY docker/php/queue-entrypoint.sh /usr/local/bin/queue-entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/queue-entrypoint.sh EXPOSE 80 # /up is Laravel's built-in health endpoint (registered in bootstrap/app.php), # so this exercises the full nginx -> php-fpm -> framework path, not just a port. HEALTHCHECK --interval=10s --timeout=5s --start-period=120s --retries=12 \ CMD curl -fsS http://127.0.0.1/up >/dev/null || exit 1 ENTRYPOINT ["entrypoint.sh"] CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]