Skip to main content

Configure Workers

The workers block in your config.cap.yaml defines background processes that run alongside your main application, for processing queues or handling long-running tasks. Each worker needs a unique name of at most 32 characters, and either a command or a subcommand to run.

workers:
  my-queue-processor:
    command: php artisan queue:work --queue=my_queue
    replicas: 1

Options

Property Type Description Default
command string or array The full command line to run, including a binary such as php. See Command or subcommand. None
subcommand string or array The command to run with your framework's binary. See Command or subcommand. The name
replicas integer The number of instances of this worker to run. Negative values are rejected. 1
resources.cpu integer or string CPU allocated to each instance. A number of cores, or thousandths such as 250m. 125m
resources.memory integer or string Memory allocated to each instance. A number of bytes, or a value with a unit such as 512Mi. 256Mi
stateful boolean Whether instances get stable hostnames and are replaced one at a time. See Stateful workers. false
terminationTimeout integer or string How long the worker has to finish its work and exit before it is forcibly stopped. See Graceful shutdown. 30
restartInterval integer or string How often the worker is stopped and started again. Values below 1 second are rejected. See Restarting workers. None
scale boolean or object Whether to match the number of instances to the length of a queue. See Autoscaling on a queue. false
scale.enable boolean Whether to match the number of instances to the length of a queue. false
scale.connection string The name of the RabbitMQ connection the queue lives on. default
scale.queue string The name of the queue that is watched. The name
scale.vector integer The number of queued messages one instance is expected to handle. 20
scale.maxReplicas integer The highest number of instances the queue can grow to. Values below replicas are rejected. 10
variables object Environment variables for this worker only, on top of the global ones. See Environment Variables. None

command and subcommand cannot both be set on the same worker.

Command or subcommand

Every worker runs one of two things. command is the full command line, exactly as you would type it. subcommand is just the part after your framework's binary, and CAP fills in the rest from your application type.

workers:
  emails:
    subcommand: queue:work --queue=emails   # php artisan queue:work --queue=emails
  legacy:
    command: php /app/bin/legacy-worker.php # run verbatim

Which binary a subcommand gets depends on the type: php artisan on Laravel, php bin/console on Symfony and Shopware, php bin/cake on CakePHP, npm run on Node. See Application Types for the full list.

Omitting both runs the worker's own name as the subcommand, so a worker named import runs php artisan import on Laravel.

For workers, subcommand also wraps the process in a supervisor that restarts it when it exits, which is what you want for a queue consumer that is meant to run forever. A command is run as given and is not restarted, so use command only when your process manages its own lifecycle.

Custom base image required for subcommands. The subcommand option relies on a binary that only exists in our base container. Use command if you build your own image.

Graceful shutdown

By default, when a rollout happens or a shutdown is requested for any other reason, a worker has 30 seconds to finish its current work and exit cleanly before being forcibly stopped. You can raise or lower this limit with terminationTimeout.

The value can be an integer (seconds) or a human-readable duration string:

workers:
  my-queue-processor:
    command: php artisan queue:work
    terminationTimeout: 300   # 5 minutes, as seconds

  my-slow-worker:
    command: php artisan process:batch
    terminationTimeout: 10m   # the same limit, as a duration string

Accepted duration string units include s (seconds), m (minutes), h (hours), and d (days), and they can be combined (e.g., '1h 30m').

Set terminationTimeout high enough to cover the longest job your worker may be processing at the time of a deployment. When the timeout expires before the process exits, the worker is stopped anyway and any in-progress work is lost.

Restarting workers

Long-running workers can become stale, for example because they hold on to memory or to a connection that has gone away. With restartInterval the worker is stopped and started again at a fixed interval, so it always runs on a fresh process.

workers:
  my-queue-processor:
    subcommand: queue:work
    restartInterval: 1h      # restart every hour

  my-slow-worker:
    subcommand: process:batch
    restartInterval: 21600   # 6 hours, as seconds

The value accepts the same formats as terminationTimeout. Without it the worker keeps running until something else stops it.

A restart follows the same steps as a normal shutdown: the worker is asked to stop, finishes the job it is working on, and is only forcibly stopped when that takes longer than terminationTimeout.

Stateful workers

By default, worker instances are interchangeable, and during an update an old and a new instance can briefly run at the same time. Setting stateful: true changes that:

  • Each instance gets a stable, predictable hostname (my-worker-0, my-worker-1).
  • Instances are replaced in order: the existing one is stopped before the new one starts.
  • The number of running instances never exceeds the configured replicas.

This is useful for workers that must not run concurrently, such as singleton background processors, or for workloads that benefit from a stable hostname.

workers:
  my-singleton-worker:
    stateful: true
    command: php artisan my-command

With multiple replicas, each instance gets a dedicated hostname:

workers:
  my-worker:
    stateful: true
    replicas: 3
    # Instances: my-worker-0, my-worker-1, my-worker-2

Stateful workers can be combined with scale. Instances are still replaced in order.

Autoscaling on a queue

CAP can match the number of worker instances to the length of a RabbitMQ queue, adding instances when a backlog builds up and removing them again when the queue drains.

workers:
  runner:
    scale: true

That watches the queue named after the worker on the default connection. Write scale as a block to tune it:

workers:
  runner:
    replicas: 2
    scale:
      enable: true
      connection: default
      queue: my-custom-queue
      vector: 20
      maxReplicas: 10

replicas is the lowest number of instances, held even when the queue is empty. The target number of instances is the queue length divided by vector, so a lower vector adds instances sooner.