From 3d7683c8aca2b8a5a63d85f3e21e9f3f22ea0f9f Mon Sep 17 00:00:00 2001 From: Javier Feliz Date: Sat, 2 Aug 2025 16:19:05 -0400 Subject: [PATCH] Add hooks as well as scheduler and queue worker config --- CLAUDE.md | 96 +++++++ Dockerfile | 12 + README.md | 409 ++++++++++++++++++++++++++++++ entrypoint.sh | 26 ++ supervisor/laravel-queue.conf | 14 + supervisor/laravel-scheduler.conf | 13 + supervisor/supervisord.conf | 18 ++ 7 files changed, 588 insertions(+) create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 supervisor/laravel-queue.conf create mode 100644 supervisor/laravel-scheduler.conf create mode 100644 supervisor/supervisord.conf diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7ff2f49 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,96 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a Docker base image for Laravel applications using FrankenPHP, designed to be lightweight and production-ready. The repository contains minimal configuration files to create a containerized Laravel runtime environment. + +## Architecture + +- **Base Image**: Uses `dunglas/frankenphp` as the foundation +- **Server**: FrankenPHP with Laravel Octane integration +- **Database**: Configured for SQLite by default (with support for PostgreSQL and MySQL extensions) +- **Application**: Expects Laravel application code to be mounted at `/app` + +## Key Files + +- `Dockerfile`: Main container configuration with PHP extensions and entrypoint setup +- `entrypoint.sh`: Container startup script that handles Laravel initialization +- `.env.docker`: Production environment configuration for containerized Laravel apps +- `.github/workflows/build.yml`: CI/CD pipeline for building and pushing to Docker registry + +## Docker Commands + +### Building the Image +```bash +docker build -t laravel-base . +``` + +### Running a Container +```bash +# Assumes Laravel application code is mounted to /app +docker run -p 8000:8000 -v /path/to/laravel/app:/app laravel-base +``` + +## Container Behavior + +The entrypoint script (`entrypoint.sh`) automatically: +1. Runs database migrations (`php artisan migrate --force`) +2. Creates storage symlink (`php artisan storage:link`) +3. Generates APP_KEY if not set in environment +4. Clears and caches configuration and views +5. Executes custom hook script if `/app/hook.sh` exists +6. Starts supervisor services (queue worker and/or scheduler) if enabled via environment variables +7. Starts FrankenPHP server on port 8000 with admin port 2019 + +## Hook System + +Applications extending this base image can provide a `hook.sh` script at `/app/hook.sh` to run custom initialization commands. The hook script is executed after Laravel setup but before the server starts. + +### Example Usage in Extending Dockerfile +```dockerfile +FROM gitgud.foo/thegrind/laravel-base:latest +COPY ./hook.sh /app/hook.sh +# Your application code and additional setup +``` + +### Example hook.sh +```bash +#!/bin/sh +# Custom application initialization +php /app/artisan db:seed --force +php /app/artisan queue:restart +``` + +## Queue Workers and Scheduler + +The base image includes supervisor configuration for Laravel queue workers and scheduler. These can be optionally enabled by extending images using environment variables: + +- `ENABLE_QUEUE_WORKER=true` - Starts Laravel queue worker process +- `ENABLE_SCHEDULER=true` - Starts Laravel scheduler process + +### Supervisor Configuration Files +- `supervisord.conf` - Main supervisor configuration +- `laravel-queue.conf` - Queue worker process configuration +- `laravel-scheduler.conf` - Scheduler process configuration + +### Usage Example +```dockerfile +FROM gitgud.foo/thegrind/laravel-base:latest +ENV ENABLE_QUEUE_WORKER=true +ENV ENABLE_SCHEDULER=true +# Your application setup... +``` + +## Environment Configuration + +The `.env.docker` file is copied into the container and includes: +- SQLite database configuration +- FrankenPHP/Octane server settings +- Production-oriented cache and session settings +- Application name: "FlowTODO" + +## Deployment + +GitHub Actions workflow triggers on release publication and pushes to `gitgud.foo/thegrind/laravel-base` registry with both `latest` and version tags. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 6224495..557ffcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,11 +9,23 @@ RUN install-php-extensions \ # Add other PHP extensions here... +# Install supervisor for queue workers and scheduler +RUN apt-get update && apt-get install -y \ + supervisor \ + cron \ + && rm -rf /var/lib/apt/lists/* + +# Set up supervisor configuration +COPY ./supervisor/ /etc/supervisor/conf.d/ + # Set up entrypoint script COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh COPY ./.env.docker /app/.env RUN chmod +x /usr/local/bin/entrypoint.sh +# Copy hook script if it exists (for extending images) +# Note: Applications extending this image can add their own hook.sh to /app/ + RUN mkdir -p /app/database RUN touch /app/database/database.sqlite && chmod 777 /app/database/database.sqlite diff --git a/README.md b/README.md new file mode 100644 index 0000000..92bbc6e --- /dev/null +++ b/README.md @@ -0,0 +1,409 @@ +# Docker Laravel Base Image + +A production-ready Docker base image for Laravel applications using FrankenPHP and Laravel Octane. This image provides a lightweight, high-performance foundation for containerized Laravel apps. + +## Features + +- **FrankenPHP**: Modern PHP application server with built-in HTTPS, HTTP/2, and HTTP/3 support +- **Laravel Octane**: High-performance application server integration +- **Multi-database Support**: Pre-installed extensions for PostgreSQL, MySQL, and SQLite +- **Queue Workers**: Optional supervisor-managed Laravel queue workers +- **Task Scheduler**: Optional supervisor-managed Laravel task scheduler +- **Production Ready**: Optimized configuration for production deployments +- **Extensible**: Hook system for custom initialization scripts +- **Automatic Setup**: Handles migrations, key generation, and caching automatically + +## Quick Start + +### 1. Create Your Laravel App Dockerfile + +In your Laravel application root, create a `Dockerfile`: + +```dockerfile +FROM gitgud.foo/thegrind/laravel-base:latest + +# Copy your Laravel application +COPY . /app + +# Install Composer dependencies +RUN composer install --no-dev --optimize-autoloader + +# Set proper permissions +RUN chown -R www-data:www-data /app/storage /app/bootstrap/cache +RUN chmod -R 775 /app/storage /app/bootstrap/cache + +EXPOSE 8000 +``` + +### 2. Build and Run + +```bash +# Build your application image +docker build -t my-laravel-app . + +# Run the container +docker run -p 8000:8000 my-laravel-app +``` + +Your Laravel application will be available at `http://localhost:8000`. + +## Environment Configuration + +The base image includes a default `.env` configuration optimized for containerized environments. You can override these settings by: + +### Option 1: Custom .env File +```dockerfile +FROM gitgud.foo/thegrind/laravel-base:latest + +# Replace the default .env with your custom one +COPY ./.env.production /app/.env + +COPY . /app +RUN composer install --no-dev --optimize-autoloader +``` + +### Option 2: Environment Variables +```bash +docker run -p 8000:8000 \ + -e APP_NAME="My Laravel App" \ + -e APP_ENV=production \ + -e DB_CONNECTION=mysql \ + -e DB_HOST=mysql \ + -e DB_DATABASE=myapp \ + my-laravel-app +``` + +## Hook System + +Extend the base image's startup process by adding a `hook.sh` script that runs after Laravel initialization but before the server starts. + +### Creating a Hook Script + +Create a `hook.sh` file in your project: + +```bash +#!/bin/sh +set -e + +echo "Running custom initialization..." + +# Seed the database +php /app/artisan db:seed --force + +# Clear and warm up caches +php /app/artisan route:cache +php /app/artisan event:cache + +# Start background workers (if using supervisor) +# supervisord -c /etc/supervisor/conf.d/supervisord.conf + +echo "Custom initialization complete!" +``` + +### Using the Hook in Your Dockerfile + +```dockerfile +FROM gitgud.foo/thegrind/laravel-base:latest + +# Copy your hook script +COPY ./hook.sh /app/hook.sh + +# Copy your Laravel application +COPY . /app + +RUN composer install --no-dev --optimize-autoloader +RUN chown -R www-data:www-data /app/storage /app/bootstrap/cache +RUN chmod -R 775 /app/storage /app/bootstrap/cache + +EXPOSE 8000 +``` + +### Hook Script Examples + +#### Database Seeding +```bash +#!/bin/sh +set -e + +# Only seed if tables are empty +if [ "$(php /app/artisan tinker --execute='echo \App\Models\User::count();')" = "0" ]; then + echo "Seeding database..." + php /app/artisan db:seed --force +fi +``` + +#### Queue Worker Setup +```bash +#!/bin/sh +set -e + +# Queue workers are managed by supervisor - just restart them +php /app/artisan queue:restart + +# Custom queue configuration can be done here +# php /app/artisan queue:prune-failed --hours=48 +``` + +#### Cache Warming +```bash +#!/bin/sh +set -e + +# Pre-compile views and routes for better performance +php /app/artisan view:cache +php /app/artisan route:cache +php /app/artisan config:cache +``` + +## Queue Workers and Scheduler + +Enable Laravel queue workers and task scheduler by setting environment variables: + +### Basic Queue Worker Setup +```dockerfile +FROM gitgud.foo/thegrind/laravel-base:latest + +# Enable queue worker +ENV ENABLE_QUEUE_WORKER=true + +COPY . /app +RUN composer install --no-dev --optimize-autoloader +``` + +### Full Setup with Scheduler +```dockerfile +FROM gitgud.foo/thegrind/laravel-base:latest + +# Enable both queue worker and scheduler +ENV ENABLE_QUEUE_WORKER=true +ENV ENABLE_SCHEDULER=true + +COPY . /app +RUN composer install --no-dev --optimize-autoloader +``` + +### Docker Compose with Queue Workers +```yaml +version: '3.8' + +services: + app: + build: . + ports: + - "8000:8000" + environment: + - APP_ENV=production + - ENABLE_QUEUE_WORKER=true + - ENABLE_SCHEDULER=true + - DB_CONNECTION=mysql + - DB_HOST=mysql + - DB_DATABASE=laravel + - DB_USERNAME=laravel + - DB_PASSWORD=secret + - QUEUE_CONNECTION=redis + - REDIS_HOST=redis + depends_on: + - mysql + - redis + + mysql: + image: mysql:8.0 + environment: + MYSQL_DATABASE: laravel + MYSQL_USER: laravel + MYSQL_PASSWORD: secret + MYSQL_ROOT_PASSWORD: secret + volumes: + - mysql_data:/var/lib/mysql + + redis: + image: redis:alpine + ports: + - "6379:6379" + +volumes: + mysql_data: +``` + +## Docker Compose Example + +For local development or multi-service deployments: + +```yaml +version: '3.8' + +services: + app: + build: . + ports: + - "8000:8000" + environment: + - APP_ENV=production + - DB_CONNECTION=mysql + - DB_HOST=mysql + - DB_DATABASE=laravel + - DB_USERNAME=laravel + - DB_PASSWORD=secret + depends_on: + - mysql + - redis + + mysql: + image: mysql:8.0 + environment: + MYSQL_DATABASE: laravel + MYSQL_USER: laravel + MYSQL_PASSWORD: secret + MYSQL_ROOT_PASSWORD: secret + volumes: + - mysql_data:/var/lib/mysql + + redis: + image: redis:alpine + ports: + - "6379:6379" + +volumes: + mysql_data: +``` + +## Advanced Configuration + +### Custom PHP Extensions + +If your application needs additional PHP extensions: + +```dockerfile +FROM gitgud.foo/thegrind/laravel-base:latest + +# Install additional PHP extensions +RUN install-php-extensions \ + gd \ + imagick \ + redis + +COPY . /app +RUN composer install --no-dev --optimize-autoloader +``` + +### Multi-stage Build + +For optimized production images: + +```dockerfile +# Build stage +FROM composer:2 as composer +COPY composer.json composer.lock /app/ +WORKDIR /app +RUN composer install --no-dev --optimize-autoloader --no-scripts + +# Production stage +FROM gitgud.foo/thegrind/laravel-base:latest + +# Copy vendor dependencies from build stage +COPY --from=composer /app/vendor /app/vendor + +# Copy application code +COPY . /app + +# Set permissions +RUN chown -R www-data:www-data /app/storage /app/bootstrap/cache +RUN chmod -R 775 /app/storage /app/bootstrap/cache + +EXPOSE 8000 +``` + +## Production Deployment + +### Health Checks + +Add health checks to your Dockerfile: + +```dockerfile +FROM gitgud.foo/thegrind/laravel-base:latest + +COPY . /app +RUN composer install --no-dev --optimize-autoloader + +# Add health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8000/health || exit 1 + +EXPOSE 8000 +``` + +### Environment Variables + +Key environment variables for production: + +- `APP_ENV=production` +- `APP_DEBUG=false` +- `APP_KEY` - Generate with `php artisan key:generate` +- `DB_*` - Database connection settings +- `CACHE_DRIVER` - Redis recommended for production +- `SESSION_DRIVER` - Redis or database recommended +- `QUEUE_CONNECTION` - Redis or database for background jobs +- `ENABLE_QUEUE_WORKER=true` - Enable Laravel queue worker +- `ENABLE_SCHEDULER=true` - Enable Laravel task scheduler + +## Troubleshooting + +### Permission Issues +```bash +# Fix storage and cache permissions +docker exec -it container-name chown -R www-data:www-data /app/storage /app/bootstrap/cache +docker exec -it container-name chmod -R 775 /app/storage /app/bootstrap/cache +``` + +### Database Connection Issues +```bash +# Check database connectivity +docker exec -it container-name php /app/artisan tinker +>>> DB::connection()->getPdo(); +``` + +### View Logs +```bash +# View application logs +docker exec -it container-name tail -f /app/storage/logs/laravel.log + +# View FrankenPHP logs +docker logs container-name + +# View supervisor logs +docker exec -it container-name tail -f /var/log/supervisor/supervisord.log + +# View queue worker logs +docker exec -it container-name tail -f /var/log/supervisor/laravel-queue.log + +# View scheduler logs +docker exec -it container-name tail -f /var/log/supervisor/laravel-scheduler.log +``` + +### Queue Worker Management +```bash +# Check supervisor status +docker exec -it container-name supervisorctl status + +# Restart queue workers +docker exec -it container-name supervisorctl restart laravel-queue:* + +# Start/stop scheduler +docker exec -it container-name supervisorctl start laravel-scheduler:* +docker exec -it container-name supervisorctl stop laravel-scheduler:* +``` + +## Base Image Details + +- **Base**: `dunglas/frankenphp` +- **PHP Extensions**: pcntl, pdo, pdo_pgsql, pdo_mysql, pdo_sqlite +- **System Packages**: supervisor, cron +- **Ports**: 8000 (HTTP), 2019 (Admin) +- **Entrypoint**: Handles migrations, key generation, caching, supervisor services, and server startup +- **Default Database**: SQLite (configurable) +- **Queue Workers**: Supervisor-managed, optional via `ENABLE_QUEUE_WORKER=true` +- **Scheduler**: Supervisor-managed, optional via `ENABLE_SCHEDULER=true` + +## Contributing + +This base image is designed to be minimal and flexible. For feature requests or issues, please refer to the project repository. \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index 868f417..d0ccba3 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -14,5 +14,31 @@ php /app/artisan config:clear php /app/artisan config:cache php /app/artisan view:cache +# Execute hook script if it exists +if [ -f "/app/hook.sh" ]; then + echo "Executing application hook script..." + chmod +x /app/hook.sh + /app/hook.sh +fi + +# Start supervisor services if enabled +if [ "$ENABLE_QUEUE_WORKER" = "true" ]; then + echo "Enabling Laravel queue worker..." + supervisorctl -c /etc/supervisor/conf.d/supervisord.conf update laravel-queue + supervisorctl -c /etc/supervisor/conf.d/supervisord.conf start laravel-queue:* +fi + +if [ "$ENABLE_SCHEDULER" = "true" ]; then + echo "Enabling Laravel scheduler..." + supervisorctl -c /etc/supervisor/conf.d/supervisord.conf update laravel-scheduler + supervisorctl -c /etc/supervisor/conf.d/supervisord.conf start laravel-scheduler:* +fi + +# Start supervisor if any services are enabled +if [ "$ENABLE_QUEUE_WORKER" = "true" ] || [ "$ENABLE_SCHEDULER" = "true" ]; then + echo "Starting supervisor..." + /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf & +fi + # exec php /app/artisan octane:frankenphp exec php /app/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port=8000 diff --git a/supervisor/laravel-queue.conf b/supervisor/laravel-queue.conf new file mode 100644 index 0000000..1b3ddea --- /dev/null +++ b/supervisor/laravel-queue.conf @@ -0,0 +1,14 @@ +[program:laravel-queue] +process_name=%(program_name)s_%(process_num)02d +command=php /app/artisan queue:work --sleep=3 --tries=3 --max-time=3600 +autostart=false +autorestart=true +stopasgroup=true +killasgroup=true +user=www-data +numprocs=1 +redirect_stderr=true +stdout_logfile=/var/log/supervisor/laravel-queue.log +stdout_logfile_maxbytes=100MB +stdout_logfile_backups=2 +stopwaitsecs=3600 \ No newline at end of file diff --git a/supervisor/laravel-scheduler.conf b/supervisor/laravel-scheduler.conf new file mode 100644 index 0000000..159803f --- /dev/null +++ b/supervisor/laravel-scheduler.conf @@ -0,0 +1,13 @@ +[program:laravel-scheduler] +process_name=%(program_name)s +command=/bin/bash -c "while [ true ]; do (php /app/artisan schedule:run --verbose --no-interaction &); sleep 60; done" +autostart=false +autorestart=true +stopasgroup=true +killasgroup=true +user=www-data +numprocs=1 +redirect_stderr=true +stdout_logfile=/var/log/supervisor/laravel-scheduler.log +stdout_logfile_maxbytes=100MB +stdout_logfile_backups=2 \ No newline at end of file diff --git a/supervisor/supervisord.conf b/supervisor/supervisord.conf new file mode 100644 index 0000000..43ed62d --- /dev/null +++ b/supervisor/supervisord.conf @@ -0,0 +1,18 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[unix_http_server] +file=/var/run/supervisor.sock +chmod=0700 + +[supervisorctl] +serverurl=unix:///var/run/supervisor.sock + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[include] +files = /etc/supervisor/conf.d/*.conf \ No newline at end of file