Laravel

Running workers

Run each Laravel background process in its own container, from the same image as your web service. Each worker container needs three settings: the Artisan command, a stop signal, and a health check for that process.

Spin Pro sets up the scheduler, queues, and Horizon on your own server with zero-downtime deployments.

Workers

WorkerCommandHealth check
Task schedulerphp artisan schedule:workhealthcheck-schedule
Queuephp artisan queue:workhealthcheck-queue
Horizonphp artisan horizonhealthcheck-horizon
Reverbphp artisan reverb:starthealthcheck-reverb
Nightwatchphp artisan nightwatch:agenthealthcheck-nightwatch

Every worker needs three settings

  • Command. The images set WORKDIR to /var/www/html (APP_BASE_DIR), so php artisan works as written. The container runs only this process: no web server, no PHP-FPM.
  • stop_signal: SIGTERM. The fpm, fpm-nginx, and fpm-apache images set STOPSIGNAL SIGQUIT, the graceful stop signal for PHP-FPM and S6 Overlay. Laravel's workers stop gracefully on SIGTERM, and Horizon doesn't handle SIGQUIT at all, so set SIGTERM on every worker. The cli and frankenphp images already stop with SIGTERM.
  • A health check. The image's own health check tests the web server or PHP-FPM, which isn't running in a worker container. Without an override, a worker built from a web variation is marked unhealthy. The cli variation has no health check. See Health checks for the scripts.

Docker kills a container 10 seconds after the stop signal. If your jobs run longer, raise stop_grace_period on the worker so it can finish the current job.

Laravel automations, including migrations, run in every container that starts with AUTORUN_ENABLED=true, whatever its command. If your workers share the web service's environment through an env_file or YAML anchor, each one migrates the database at startup. Set AUTORUN_ENABLED=true on one service only.

Full Compose example

This example builds on an application image made from serversideup/php:8.5-fpm-nginx. Run queue or horizon, not both: Horizon starts its own queue workers.

compose.yml
services:
  app:
    image: my-org/my-app:1.0.0
    ports:
      - "80:8080"
    environment:
      AUTORUN_ENABLED: "true"

  horizon:
    image: my-org/my-app:1.0.0
    command: ["php", "artisan", "horizon"]
    stop_signal: SIGTERM
    healthcheck:
      test: ["CMD", "healthcheck-horizon"]
      start_period: 10s
    depends_on:
      - redis

  scheduler:
    image: my-org/my-app:1.0.0
    command: ["php", "artisan", "schedule:work"]
    stop_signal: SIGTERM
    healthcheck:
      test: ["CMD", "healthcheck-schedule"]
      start_period: 10s

  redis:
    image: redis:8-alpine

Run a worker inside the web container

To run a worker in the same container as the web server instead, add it as an S6 Overlay service in fpm-nginx or fpm-apache. See Using S6 Overlay. Separate containers are easier to scale and restart on their own.