# ── Stage 1: Composer deps ─────────────────────────────────────────────────── FROM composer:2 AS composer-build WORKDIR /app COPY composer.json composer.lock ./ RUN composer install \ --no-dev \ --no-interaction \ --no-scripts \ --prefer-dist \ --optimize-autoloader \ --ignore-platform-reqs # ── Stage 2a: Vite dev (node_modules only, no build) ───────────────────────── FROM node:22-alpine AS vite-dev WORKDIR /app COPY package*.json ./ RUN npm ci --ignore-scripts # ── Stage 2b: Node / Vite build ────────────────────────────────────────────── FROM node:22-alpine AS node-build WORKDIR /app COPY package*.json ./ RUN npm ci --ignore-scripts COPY . . COPY --from=composer-build /app/vendor ./vendor RUN npm run build # ── Stage 3: PHP base (Ubuntu + ondrej/php — pre-compiled, no source builds) ─ FROM ubuntu:24.04 AS php-base ENV DEBIAN_FRONTEND=noninteractive ENV TZ=UTC RUN apt-get update \ && apt-get install -y --no-install-recommends software-properties-common ca-certificates gnupg curl \ && add-apt-repository ppa:ondrej/php \ && curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \ | gpg --dearmor -o /usr/share/keyrings/pgdg.gpg \ && echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] https://apt.postgresql.org/pub/repos/apt noble-pgdg main" \ > /etc/apt/sources.list.d/pgdg.list \ && apt-get update \ && apt-get install -y --no-install-recommends \ php8.5-fpm \ php8.5-cli \ php8.5-pgsql \ php8.5-intl \ php8.5-zip \ php8.5-gd \ php8.5-bcmath \ php8.5-imagick \ php8.5-exif \ php8.5-mbstring \ php8.5-xml \ php8.5-curl \ php8.5-redis \ postgresql-client-18 \ && rm -rf /var/lib/apt/lists/* \ && mkdir -p /run/php \ && sed -i 's|listen = /run/php/php8.5-fpm.sock|listen = 9000|' /etc/php/8.5/fpm/pool.d/www.conf \ && echo "clear_env = no" >> /etc/php/8.5/fpm/pool.d/www.conf RUN apt-get update \ && apt-get install -y --no-install-recommends php8.5-dev php-pear autoconf gcc make \ && pecl install opentelemetry \ && echo "extension=opentelemetry.so" > /etc/php/8.5/mods-available/opentelemetry.ini \ && phpenmod opentelemetry \ && apt-get purge -y --auto-remove php8.5-dev php-pear autoconf gcc make \ && rm -rf /var/lib/apt/lists/* # ── Stage 4: Production ─────────────────────────────────────────────────────── FROM php-base AS production COPY docker/php/php.ini /etc/php/8.5/fpm/conf.d/99-app.ini COPY docker/php/php.ini /etc/php/8.5/cli/conf.d/99-app.ini COPY docker/php/fpm-pool.conf /etc/php/8.5/fpm/pool.d/zz-app.conf WORKDIR /var/www/html COPY --from=composer-build /app/vendor ./vendor COPY --from=node-build /app/public/build ./public/build COPY . . RUN chown -R www-data:www-data storage bootstrap/cache \ && chmod -R 775 storage bootstrap/cache # Package discovery and asset publishing are fully determined by composer.lock + code, # so they belong in the image, not in every container's entrypoint. Only asset-type # tags are force-republished here — config tags (core-config, lunar) are a deliberate # one-time `vendor:publish` step you run and commit by hand, since re-running them would # silently overwrite any local edits to config/core.php or config/lunar/*.php. RUN php artisan package:discover --ansi \ && php artisan vendor:publish --tag=core-assets --force --ansi \ && php artisan vendor:publish --tag=public --force --ansi \ && php artisan filament:assets --ansi COPY docker/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 9000 ENTRYPOINT ["/entrypoint.sh"] CMD ["php-fpm8.5", "-F"] # ── Stage 5: Nginx (public assets baked in from production) ────────────────── FROM nginx:alpine AS nginx COPY docker/nginx/prod.conf /etc/nginx/conf.d/default.conf COPY --from=production /var/www/html/public /var/www/html/public # ── Stage 6: Development ────────────────────────────────────────────────────── FROM php-base AS development RUN apt-get update && apt-get install -y --no-install-recommends git unzip curl \ && rm -rf /var/lib/apt/lists/* COPY --from=composer:2 /usr/bin/composer /usr/bin/composer COPY docker/php/php.dev.ini /etc/php/8.5/fpm/conf.d/99-app.ini COPY docker/php/php.dev.ini /etc/php/8.5/cli/conf.d/99-app.ini WORKDIR /var/www/html COPY composer.json composer.lock ./ RUN composer install \ --no-interaction \ --no-scripts \ --prefer-dist \ --ignore-platform-reqs COPY docker/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 9000 ENTRYPOINT ["/entrypoint.sh"] CMD ["php-fpm8.5", "-F"]