# Production Dockerfile with FrankenPHP FROM dunglas/frankenphp:latest-php8.3-alpine # Install system dependencies RUN apk add --no-cache \ nodejs \ npm \ git \ mysql-client # Install PHP extensions RUN install-php-extensions \ pdo_mysql \ opcache \ zip \ gd \ intl # Install Composer COPY --from=composer:2 /usr/bin/composer /usr/bin/composer # Set working directory WORKDIR /app # Set fixed production environment variables ENV APP_ENV=production \ APP_DEBUG=false \ DB_CONNECTION=mysql \ DB_HOST=db \ DB_PORT=3306 \ SESSION_DRIVER=database \ CACHE_DRIVER=file \ QUEUE_CONNECTION=database \ LOG_CHANNEL=stack \ LOG_LEVEL=error \ MAIL_MAILER=smtp \ MAIL_ENCRYPTION=tls # Copy application code first COPY . . # Install PHP dependencies (production only) RUN composer install --no-dev --no-interaction --optimize-autoloader # Install ALL Node dependencies (including dev for building) RUN npm ci # Build frontend assets RUN npm run build # Remove node_modules after build to save space RUN rm -rf node_modules # Laravel optimizations RUN php artisan config:cache \ && php artisan route:cache \ && composer dump-autoload --optimize # Set permissions RUN chown -R www-data:www-data /app/storage /app/bootstrap/cache # Configure Caddy RUN cat > /etc/caddy/Caddyfile < /start-prod.sh <<'EOF' #!/bin/sh set -e # Wait for database to be ready echo "Waiting for database..." for i in $(seq 1 30); do if php artisan db:monitor --database=mysql 2>/dev/null | grep -q "OK"; then echo "Database is ready!" break fi echo "Waiting for database... ($i/30)" sleep 2 done # Run migrations echo "Running migrations..." php artisan migrate --force || echo "Migrations failed or already up-to-date" # Start FrankenPHP exec frankenphp run --config /etc/caddy/Caddyfile EOF RUN chmod +x /start-prod.sh # Start with our script CMD ["/start-prod.sh"]