Вы уже умеете разрабатывать в Docker с помощью Laravel Sail, а курс Docker показывает образ php-fpm + Nginx и воркеры в контейнерах. Путь через VPS, Supervisor и Forge разобран в темах деплой на VPS, Supervisor и cron и Forge/Envoyer/CI. Здесь только то, что специфично для продакшн-контейнера Laravel: образ Octane на FrankenPHP, что выполняется при сборке и при старте, миграции как шаг деплоя, health check и единый compose.yaml.
Один образ, несколько ролей
Релиз Laravel — это один образ, запущенный с разными командами. Веб-сервер, воркер очереди, планировщик и миграция — всё это php artisan ... над одним и тем же кодом, поэтому версии не должны расходиться.
| Роль | Команда | Время жизни |
|---|---|---|
web | php artisan octane:frankenphp --host=0.0.0.0 --port=8000 | долгоживущий, масштабируется репликами |
worker | php artisan queue:work --tries=3 --max-time=3600 | долгоживущий, сам перезапускается раз в час |
scheduler | php artisan schedule:work | ровно один экземпляр |
migrate | php artisan migrate --force --isolated | одноразовый, завершается |
💡
schedule:workзаменяет строку* * * * *в cron внутри контейнера; запускайте один планировщик, иначе задачи сработают дважды. Настройка воркеров — в теме очереди и Redis.
Продакшн-Dockerfile
Файл строится на трёх решениях: отдельная стадия Node для ассетов Vite (Node в продакшн не попадает), зависимости ставятся раньше кода (кэшируемый слой) и в конце используется не-root пользователь. Предполагается, что в проекте уже выполнены composer require laravel/octane и php artisan octane:install --server=frankenphp.
FROM node:22-alpine AS assets
WORKDIR /app
COPY . .
RUN npm ci && npm run build
FROM dunglas/frankenphp:1-php8.4
RUN install-php-extensions pcntl pdo_mysql redis opcache bcmath
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader
COPY . .
COPY --from=assets /app/public/build ./public/build
RUN composer dump-autoload --optimize --no-dev \
&& php artisan package:discover \
&& php artisan route:cache \
&& php artisan view:cache \
&& php artisan event:cacheВторая половина задаёт продакшн-значения, снимает root и объявляет команду запуска и health check: