diff --git a/build/Dockerfile b/build/Dockerfile index d78669e..3bfc359 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,44 +1,145 @@ -FROM php:8.4-fpm-alpine +# ============================================================================= +# 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 -RUN apk add --no-cache \ - curl \ - git \ - zip \ - unzip \ - libzip-dev \ - imagemagick \ - libreoffice \ - libreoffice-lang-de \ - ghostscript \ - poppler-utils \ - sqlite \ - sqlite-dev \ - postgresql-client \ - mysql-client \ - nodejs \ - npm \ - fcgi +# 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/* -RUN docker-php-ext-install \ - pdo_sqlite \ - pdo_mysql \ - zip \ - bcmath +# 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 -COPY --from=composer:latest /usr/bin/composer /usr/bin/composer +# 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 -COPY build/fpm-healthcheck.conf /usr/local/etc/php-fpm.d/zz-healthcheck.conf -COPY build/php-error-logging.conf /usr/local/etc/php-fpm.d/zz-error-logging.conf -COPY build/php-errors.ini /usr/local/etc/php/conf.d/errors.ini -COPY build/entrypoint.sh /usr/local/bin/entrypoint.sh -RUN chmod +x /usr/local/bin/entrypoint.sh +# ── 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 --interval=30s --timeout=10s --start-period=30s --retries=3 \ - CMD SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000 | grep -q pong || exit 1 +# 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 -ENTRYPOINT ["entrypoint.sh"] -CMD ["php-fpm"] +# 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"]