Queue workers run php artisan queue:work for a Laravel container app, or the command you give them for any other stack, such as bundle exec sidekiq, celery -A app worker or a Procfile worker: line. They are separate instances of your app's own image, so long jobs never slow down web requests and keep running while the web instances sleep. Workers pull jobs from your app's dply Valkey store or its dply Postgres or MySQL database, the same way queue:work does on a server.
Note
Available on every plan. Workers do not run without a plan.
If you would rather not run workers at all, a Queues resource pushes each batch of jobs into your app over HTTP and scales to zero with it. Use workers when jobs are long, need steady throughput, or rely on the Redis or database queue drivers.
Requirements
- The app uses Container delivery.
- On a stack other than Laravel, a command for each worker. dply suggests one from your
Procfile, Sidekiq, Solid Queue, GoodJob or Celery when it finds them. - On Laravel, a queue store both the app and the workers can reach: dply Valkey, or a dply Postgres or MySQL database. SQLite lives inside one container and cannot be shared.
If the app has no shared queue store, adding workers offers to start one for you (below). Workers you added earlier show Needs setup with an Add dply Valkey for the queue button that starts it and deploys.
Add queue workers
- Open your app's Overview and select Add resource.
- Choose Queue workers. The Queue workers sheet opens with three choices:
- Queues: prefilled with the queue names dply found in your code at the last build, most urgent first. It reads
config/horizon.php, thequeuevalues inconfig/queue.php, andonQueue('…')or$queue = '…'inapp/androutes/. Edit the list as you like. If Horizon defines several supervisors and your plan has worker groups, each extra supervisor gets its own workers. - When workers run: Start when jobs arrive (nothing runs while the queue is empty, and a waiting job starts a worker in a few seconds) or Always on (one worker runs all the time). Each shows its monthly cost.
- Processes: jobs each worker runs at once, prefilled for the app's size: one per 96 MB after a 192 MB reserve (1 to 8; 8 on 1 GB).
- Queues: prefilled with the queue names dply found in your code at the last build, most urgent first. It reads
- If the app uses SQLite, the sheet adds dply Valkey for the queue: the smallest size, which sleeps when idle. Choose Use a Postgres or MySQL database instead to use a database.
- Choose Add and deploy, or Add, deploy later to deploy yourself.
Timeouts, tries, memory and the other options below keep their defaults. Change them in the workers' settings afterwards.
| Plan | Start when jobs arrive | Most workers |
|---|---|---|
| Starter | Yes, its one worker | 1 |
| Pro | Yes, up to 2 when first added | 5 |
| Team | Yes, up to 2 when first added | 10 |
When workers are running, dply sets QUEUE_CONNECTION to the connection they pull from, unless you set it yourself under Environment, so the jobs your app dispatches reach the workers.
Settings
| Setting | Range | Default | What it does |
|---|---|---|---|
| Instances | 1–10 (0–10 with autoscaling, labeled Always on) | 1 | Worker instances that always run. |
| Processes | 1–8 per instance | one per 96 MB of memory (8 on 1 GB) | queue:work loops in each instance. Jobs mostly wait on a database or an API, so one instance runs several. |
| Connection | Automatic, redis, database |
Automatic | Automatic uses Redis when the app has dply Valkey it can reach, otherwise its database. |
| Queues | comma-separated | default |
Passed as --queue, in priority order, for example high,default. |
| Worker size | the container sizes (PHP apps from 0.25 vCPU) | Same as the app | The size of each worker instance. Each size shows its monthly price per always-on instance. |
Under Worker options:
| Option | Range | Default | queue:work flag |
|---|---|---|---|
| Timeout (s) | 1–3600 | 60 | --timeout |
| Tries | 1–25 | 3 | --tries |
| Sleep when empty (s) | 1–60 | 3 | --sleep |
| Memory (MB) | 64–2048 | 128 | --memory |
| Restart after (s) | 60–86400 | 3600 | --max-time |
The sheet shows the exact command your workers run.
Workers use the app's instance size (see Container apps) unless you choose a Worker size. A worker rarely needs the web container's memory, and memory is most of what an always-on instance costs, so a smaller size usually cuts the workers' bill by two thirds or more. The change applies on the next deploy, which moves the running workers to the new size; the old ones stop once the new ones start. Each instance is named worker-0, worker-1, … and runs your image with DPLY_ROLE=worker. On stop, a worker lets its current job finish. A worker that exits right after starting is restarted with a back-off.
Note
With Connection set to database on a dply database that can sleep, the workers check it every Sleep when empty seconds, so the database never sleeps while they run. The sheet offers Queue on dply Valkey instead.
Autoscaling
Turn on Autoscale to add workers when jobs back up:
- Always on: instances that always run.
0lets the workers scale to zero: one starts when jobs arrive and stops five minutes after the queue empties. - Up to: the most instances that can run.
- Add one at: jobs waiting per process before another instance starts (1–1000, default 10).
- Or when a job waits: also add an instance when the oldest ready job has waited this many seconds (0–3600, default 60;
0turns it off).
dply reads the backlog about every 10 seconds, straight from the queue store, so checking never wakes your web instances. It scales up at once and scales down after five quiet minutes. While delayed jobs exist, at least one worker stays. A dply store that is asleep holds no new jobs, so it is not woken to be checked.
The cost line under the settings shows the monthly range, from the always-on count to the maximum. Extra instances are billed only while they run.
Worker groups
A group is another set of workers for other queues, sized and scaled on its own, so a flood on one queue cannot hold up another. Under Groups, select Add a group, then set its Queues, Instances, Processes, Autoscale and up to. Its instances are named worker-{group}-N. A group shares the main group's connection and Worker options.
Plan limits
Limits apply per app, across the main group and every extra group.
| Starter | Pro | Team | |
|---|---|---|---|
| Sites | Unlimited | Unlimited | Unlimited |
| Concurrent builds | 1 | 2 | 5 |
| Build timeout | 20 min | 45 min | 60 min |
| Custom domains (per organization) | 3 | 20 | 100 |
| Container app instances | 1 per app | Autoscaling | Autoscaling |
| Queue workers per app | 1, starts when jobs arrive | 5, autoscaling | 10, autoscaling |
| SQL databases (D1) | 2 | 10 | 50 |
| Queues | 2 | 10 | 50 |
| Realtime connections per app | 200 | 1,000 | 5,000 |
| Audit log | No | No | Yes |
If your settings ask for more than your plan allows, the sheet says so and only what fits is deployed: extra groups are dropped and instances are trimmed, main group first.
Workers are billed as container compute on the app's size.
Check, pause and test
In the Queue workers sheet:
- Check workers shows the jobs waiting on each queue and the failed-job count, and lists every worker with its state (Running, Stopping, Stopped, Exited, Idle (scaled down), Paused), where it is running and its round trip to the database and Redis.
- Start stopped workers starts any worker that should be running and is not.
- Send test job queues
php artisan inspireon the first queue. - Pause stops every worker, letting running jobs finish. Jobs wait on the queue until you Resume. Deploys and autoscaling leave paused workers stopped. Pausing also pauses a scheduler running in
worker-0. - Remove takes the workers away on the next deploy.
The app's Jobs section (under Extend) summarizes the workers next to the app's queues: processes and instances, the queues and connection they read, extra groups, whether the scheduler runs in a worker, and live N of M running with the failed-job count. Its controls link back to this sheet on Overview.
Failed jobs
Select Failed jobs in the sheet to read your app's own failed-job store: each job's class, queue, connection, when it failed, attempts, error and stack trace. Retry puts a job back on its queue; Delete removes it. Retry all and Delete all act on every failed job.
Logs
Select Logs in the sheet for the last hour of worker output, newest first: each job as it runs and finishes (RUNNING, DONE, FAIL), and each worker starting, restarting and stopping. Lines arrive about a minute after they happen. Build & deploy logs → What your app is printing also filters by queue worker.
Alerts
Two notification events cover workers. Route them under Notification channels:
- Queue jobs failing: jobs logged
FAILin the last few minutes. - Queue workers keep exiting: workers exited right after starting three or more times.
Each is sent at most once every 30 minutes per app.
The scheduler in a worker
With the Laravel scheduler on and at least one always-on worker, the scheduler runs as php artisan schedule:work in worker-0, so the web instances can sleep. See Scheduled tasks.