# syntax=docker/dockerfile:1.7 # ============================================================================== # composer-deps — install PHP deps (no scripts, no dev) # ============================================================================== FROM composer:2 AS composer-deps WORKDIR /app COPY website/composer.json website/composer.lock ./ RUN composer install \ --no-dev \ --no-scripts \ --no-autoloader \ --prefer-dist \ --no-interaction \ --no-progress \ --ignore-platform-reqs COPY website/ ./ RUN composer dump-autoload --optimize --classmap-authoritative # ============================================================================== # node-build — compile assets (Vite) # ============================================================================== FROM node:24-alpine AS node-build WORKDIR /app COPY website/package.json website/package-lock.json* ./ RUN npm ci --no-audit --no-fund COPY website/ ./ # vendor/ is needed because Vite reads Laravel's helpers via @vite() COPY --from=composer-deps /app/vendor ./vendor RUN npm run build # ============================================================================== # runtime-base — common PHP 8.3-FPM image with extensions # ============================================================================== FROM php:8.3-fpm-bookworm AS runtime-base ENV COMPOSER_ALLOW_SUPERUSER=1 \ COMPOSER_NO_INTERACTION=1 RUN apt-get update \ && apt-get install -y --no-install-recommends \ git unzip curl ca-certificates \ libzip-dev libpng-dev libjpeg-dev libfreetype6-dev libwebp-dev \ libicu-dev libonig-dev libxml2-dev libcurl4-openssl-dev libssl-dev \ pkg-config \ && rm -rf /var/lib/apt/lists/* RUN docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install -j"$(nproc)" \ pdo_mysql intl bcmath gd zip pcntl posix exif sockets opcache RUN pecl install redis \ && docker-php-ext-enable redis # Production PHP / opcache config RUN { \ echo 'memory_limit=512M'; \ echo 'upload_max_filesize=50M'; \ echo 'post_max_size=50M'; \ echo 'max_execution_time=120'; \ echo 'expose_php=Off'; \ } > /usr/local/etc/php/conf.d/zz-app.ini RUN { \ echo 'opcache.enable=1'; \ echo 'opcache.enable_cli=0'; \ echo 'opcache.validate_timestamps=0'; \ echo 'opcache.memory_consumption=256'; \ echo 'opcache.interned_strings_buffer=16'; \ echo 'opcache.max_accelerated_files=20000'; \ echo 'opcache.preload_user=www-data'; \ } > /usr/local/etc/php/conf.d/zz-opcache.ini # php-fpm pool — listen on 0.0.0.0:9000 (sidecar nginx connects to localhost) RUN { \ echo '[www]'; \ echo 'user = www-data'; \ echo 'group = www-data'; \ echo 'listen = 0.0.0.0:9000'; \ echo 'pm = dynamic'; \ echo 'pm.max_children = 20'; \ echo 'pm.start_servers = 4'; \ echo 'pm.min_spare_servers = 2'; \ echo 'pm.max_spare_servers = 6'; \ echo 'pm.max_requests = 500'; \ echo 'clear_env = no'; \ } > /usr/local/etc/php-fpm.d/zz-app.conf WORKDIR /var/www/html # Bring in source + autoloaded vendor + built assets COPY --chown=www-data:www-data --from=composer-deps /app /var/www/html COPY --chown=www-data:www-data --from=node-build /app/public/build /var/www/html/public/build # Cache config & routes at build time. Skip if no APP_KEY available. # These are best-effort — caches will rebuild at runtime if needed. RUN php artisan config:clear || true \ && php artisan route:clear || true \ && php artisan view:clear || true # Entrypoint COPY docker/prod-entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh USER www-data ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] # ============================================================================== # app — php-fpm (paired with nginx sidecar in k8s) # ============================================================================== FROM runtime-base AS app EXPOSE 9000 CMD ["php-fpm"] # ============================================================================== # horizon — queue worker # ============================================================================== FROM runtime-base AS horizon STOPSIGNAL SIGTERM CMD ["php", "artisan", "horizon"] # ============================================================================== # scheduler — schedule:work loop # ============================================================================== FROM runtime-base AS scheduler CMD ["php", "artisan", "schedule:work", "--no-interaction"]