# ============================================================================= # Stage 1: Build # Installs all PHP + Node dependencies and builds Vite assets. # This stage is NOT used in production — only its outputs are copied. # ============================================================================= FROM php:8.4-cli-bookworm AS build # System deps for build stage RUN apt-get update && apt-get install -y --no-install-recommends \ git \ unzip \ zip \ curl \ libzip-dev \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ && rm -rf /var/lib/apt/lists/* # PHP extensions needed for composer install / post-autoload-dump RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install gd zip # Install Node.js 20 LTS via NodeSource RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* # Install Composer COPY --from=composer:2 /usr/bin/composer /usr/bin/composer WORKDIR /app # ── PHP dependency layer (cached until composer.lock changes) ──────────────── COPY composer.json composer.lock ./ RUN composer install \ --no-dev \ --optimize-autoloader \ --no-scripts \ --no-interaction \ --prefer-dist # ── Node dependency layer (cached until package-lock.json changes) ─────────── COPY package.json package-lock.json ./ RUN npm ci --ignore-scripts # ── Full source + build ────────────────────────────────────────────────────── COPY . . # Run composer post-autoload-dump scripts (package discovery etc.) RUN composer run-script post-autoload-dump --no-interaction || true # Build Vite assets (outputs to public/build/) RUN npm run build # Copy public/ → public-build/ so the production stage retains built assets # even after public/ becomes a runtime bind-mount target. RUN cp -r public/ public-build/ # ============================================================================= # Stage 2: Production # PHP-FPM runtime image with all system dependencies. # Node.js is NOT present here. # ============================================================================= FROM php:8.4-fpm-bookworm AS production # System packages (excluding Node.js — not needed at runtime) RUN apt-get update && apt-get install -y --no-install-recommends \ supervisor \ libzip-dev \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ libsqlite3-dev \ default-mysql-client \ ghostscript \ poppler-utils \ sqlite3 \ libfcgi-bin \ unzip \ zip \ curl \ git \ && rm -rf /var/lib/apt/lists/* # LibreOffice for PowerPoint → PDF conversion (large layer, separate cache) RUN apt-get update && apt-get install -y --no-install-recommends \ libreoffice-impress \ libreoffice-l10n-de \ && rm -rf /var/lib/apt/lists/* # PHP extensions RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install \ gd \ pdo_sqlite \ pdo_mysql \ zip \ bcmath # Install Composer (needed for artisan commands, not for installing packages) COPY --from=composer:2 /usr/bin/composer /usr/bin/composer WORKDIR /app # ── Copy built application from build stage ────────────────────────────────── COPY --from=build /app /app # Remove node_modules — not needed at runtime RUN rm -rf /app/node_modules # ── Install Docker config files ────────────────────────────────────────────── # Supervisord (manages php-fpm, queue:work, schedule:work) COPY build/supervisord.conf /etc/supervisor/conf.d/supervisord.conf # PHP runtime config (upload limits, error logging, timezone) COPY build/php.ini /usr/local/etc/php/conf.d/zz-app.ini # FPM pool config (replaces default www pool — sets ping endpoint, clear_env=no) COPY build/fpm-pool.conf /usr/local/etc/php-fpm.d/zz-www.conf # FPM error logging to stderr COPY build/fpm-error-logging.conf /usr/local/etc/php-fpm.d/zz-error-logging.conf # Remove default FPM pool config (replaced by our zz-www.conf above) RUN rm -f /usr/local/etc/php-fpm.d/www.conf /usr/local/etc/php-fpm.d/www.conf.default # ── Boot / Init scripts ─────────────────────────────────────────────────────── RUN chmod +x /app/build/boot-container.sh /app/build/init-app.sh # ── Runtime setup ───────────────────────────────────────────────────────────── EXPOSE 9000 # Healthcheck: FPM ping endpoint (starts checking after 60s to allow boot) HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET \ cgi-fcgi -bind -connect 127.0.0.1:9000 2>/dev/null | grep -q "pong" || exit 1 # boot-container.sh runs as root: creates dirs, sets permissions, # creates DB on first run, runs migrations, runs cache commands, # syncs built assets to the bind-mounted public/ directory, # then exec's supervisord (CMD). ENTRYPOINT ["/app/build/boot-container.sh"] CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]