Files
website/Dockerfile
Andrew 548fc5f1ee fix(docker): drop git from runtime, remove orphaned opcache.preload_user
Production runtime image doesn't need git (composer install runs in a
separate stage); cuts a non-trivial CVE surface. opcache.preload_user
without opcache.preload produces a startup warning — drop it; we don't
have a preload script.

Image still builds cleanly and php-fpm boots without warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 23:07:59 -04:00

127 lines
4.5 KiB
Docker

# 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 \
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'; \
} > /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"]