feat(docker): production multi-stage Dockerfile
Three named targets (app, horizon, scheduler) sharing a runtime-base with PHP 8.3-FPM, opcache, redis, and pinned php-fpm pool config. Composer + Node build stages are separate so vendor/ and public/build/ are baked into the runtime image. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
30
.dockerignore
Normal file
30
.dockerignore
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# .dockerignore
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.github
|
||||||
|
.gitea
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
.claude
|
||||||
|
.superpowers
|
||||||
|
.playwright-mcp
|
||||||
|
docker
|
||||||
|
!docker/prod-entrypoint.sh
|
||||||
|
docker-compose*.yml
|
||||||
|
.env*
|
||||||
|
!website/.env.example
|
||||||
|
docs
|
||||||
|
helm
|
||||||
|
node_modules
|
||||||
|
website/node_modules
|
||||||
|
website/vendor
|
||||||
|
website/storage/logs/*
|
||||||
|
website/storage/framework/cache/data/*
|
||||||
|
website/storage/framework/sessions/*
|
||||||
|
website/storage/framework/views/*
|
||||||
|
website/.phpunit.cache
|
||||||
|
website/tests
|
||||||
|
*.md
|
||||||
|
Makefile
|
||||||
|
scripts/whmcs-migrate
|
||||||
|
ipv4-outreach-tickets.txt
|
||||||
127
Dockerfile
Normal file
127
Dockerfile
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# 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"]
|
||||||
22
docker/prod-entrypoint.sh
Executable file
22
docker/prod-entrypoint.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd /var/www/html
|
||||||
|
|
||||||
|
# Install Passport keys from Secret-mounted location, if present.
|
||||||
|
# Chart mounts the keys at /var/www/html/secrets/oauth-{public,private}.key.
|
||||||
|
if [ -f /var/www/html/secrets/oauth-private.key ] \
|
||||||
|
&& [ -f /var/www/html/secrets/oauth-public.key ]; then
|
||||||
|
cp /var/www/html/secrets/oauth-private.key storage/oauth-private.key
|
||||||
|
cp /var/www/html/secrets/oauth-public.key storage/oauth-public.key
|
||||||
|
chmod 600 storage/oauth-private.key storage/oauth-public.key
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Refresh Laravel caches against current env. Safe to run on every boot —
|
||||||
|
# config/route/view caching is per-pod and idempotent.
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
php artisan event:cache || true
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
Reference in New Issue
Block a user