> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sevalla.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These are the docs for Sevalla, a platform for deploying applications, databases, static sites, object storage and load balancers. The dashboard is at https://app.sevalla.com.
> Bold text in steps is an exact dashboard label.
> To act on a Sevalla account, prefer the Sevalla MCP server (https://mcp.sevalla.com/mcp, see https://docs.sevalla.com/ai-agents/overview.md), then the Sevalla CLI (https://docs.sevalla.com/developer-tools/cli.md), then the REST API (https://api-docs.sevalla.com, OpenAPI spec at https://api.sevalla.com/v3/openapi.json).
> Every page index is at https://docs.sevalla.com/llms.txt.

# Run Rails background jobs

> Run Active Job with Solid Queue on Sevalla: bin/jobs in a background worker or Solid Queue inside Puma, recurring tasks, and what happens to running jobs on each deployment.

A background job is work that runs outside the request, such as sending email or importing a file. Rails queues it with Active Job, and in a new Rails 8 application Solid Queue runs it: the queue lives in tables of the application's PostgreSQL database, and Solid Queue's processes pick the jobs up. On Sevalla, those processes run in a background worker, from the same image as the web process with its own start command, or inside Puma in the web process.

## At a glance

What this guide adds to the application from [Deploy a Rails application](https://docs.sevalla.com/guides/ruby/rails/deploy.md#at-a-glance):

| | |
| - | - |
| Processes | `Jobs` background worker (`exec bin/jobs`), or Solid Queue inside Puma in the web process |
| Services | The deploy guide's PostgreSQL database holds the queue |
| Size | **H1** for the worker; **S1** or larger for the web process when Solid Queue runs inside Puma |

### Environment variables

| Variable | Required | Available during | Secret | Value |
| - | - | - | - | - |
| `SOLID_QUEUE_IN_PUMA` | No | Runtime | No | `1` to run Solid Queue inside Puma instead of in the `Jobs` worker |

The worker reads the same variables as the web process, listed in [Deploy a Rails application](https://docs.sevalla.com/guides/ruby/rails/deploy.md#environment-variables).

## Before you begin

* A Rails application deployed on Sevalla, as in [Deploy a Rails application](https://docs.sevalla.com/guides/ruby/rails/deploy.md). The deploy guide creates Solid Queue's tables in the application's database, and `config/environments/production.rb` already sets `config.active_job.queue_adapter = :solid_queue`.

## Create a job

Generate a job:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
bin/rails generate job OrderConfirmation
```

Replace the generated `perform` method with the work the job does:

```ruby app/jobs/order_confirmation_job.rb theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
class OrderConfirmationJob < ApplicationJob
  queue_as :default

  def perform(order_id)
    logger.info "Sending the confirmation for order #{order_id}"
  end
end
```

Queue it from a controller, a model or anywhere else with `OrderConfirmationJob.perform_later(order.id)`. `perform_later` writes the job to the database and returns at once. Commit and push the changes.

## Run the jobs in a background worker

**1. Create the background worker**

On the application's **Processes** tab, click **Create process**. Choose **Background worker**, enter `Jobs` as the **Name** and `exec bin/jobs` as the **Custom start command**. Keep the **Resource** at **H1** or choose a larger one, and click **Create process**.

**2. Deploy**

Click **Deploy** in the undeployed changes bar at the bottom of the page.

![The Create process sheet with Background worker selected, the Name and Custom start command fields, the resource and the scaling settings](https://assets.sevalla.com/screenshots/light/app-processes-worker-create.png)

`bin/jobs` starts Solid Queue's supervisor, which runs three processes: a worker that runs the jobs with 3 threads (set in `config/queue.yml`), a dispatcher, and a scheduler for [recurring tasks](#run-recurring-tasks). Together they used 170 MB to 250 MB of memory at rest on **H1** (0.3 GB) in this guide's tests; choose a larger size for jobs that need more. To run more jobs at once, add instances in the process's **Scaling** section. See [Scaling](https://docs.sevalla.com/applications/scaling.md).

Keep `exec` in the start command. Sevalla runs a custom start command with `/bin/sh -l -c`, and the shell stays the container's main process unless `exec` replaces it with Solid Queue. The shell doesn't pass on the `SIGTERM` that Sevalla sends when it replaces an instance, so without `exec`, a job that's running when you deploy is killed and marked as failed.

## Run the jobs inside Puma

Instead of a background worker, the web process can run Solid Queue: Rails' `config/puma.rb` loads Solid Queue's Puma plugin when the `SOLID_QUEUE_IN_PUMA` environment variable is set, and the plugin starts the supervisor with Puma. It saves a process, but the web process then holds Puma and Solid Queue's four processes.

**1. Add the variable**

On the application's **Environment variables** tab, click **Add environment variables**, add `SOLID_QUEUE_IN_PUMA` with the value `1`, and click **Add environment variables**.

**2. Give the web process more memory**

On the **Processes** tab, click **⋯** on the **Web** process, then click **Update process**. Choose **S1** or a larger size and click **Update process**.

**3. Deploy**

Click **Deploy** in the undeployed changes bar at the bottom of the page. Don't create the `Jobs` worker, or delete it if you created it.

On **H1**, Puma and Solid Queue use about 245 MB of the 0.3 GB at rest: in this guide's tests, a `bin/rails runner` command in the web terminal ran out of memory, which restarted the web instance. Each web instance runs its own Solid Queue processes.

## Run recurring tasks

Solid Queue's scheduler queues jobs on a schedule from `config/recurring.yml`. Add a job that reports the size of the catalog, then schedule it under `production`:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
bin/rails generate job InventoryReport
```

```ruby app/jobs/inventory_report_job.rb theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
class InventoryReportJob < ApplicationJob
  queue_as :default

  def perform
    logger.info "#{Product.count} products in the catalog"
  end
end
```

```yaml config/recurring.yml theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
production:
  clear_solid_queue_finished_jobs:
    command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)"
    schedule: every hour at minute 12
  inventory_report:
    class: InventoryReportJob
    schedule: every 5 minutes
```

`clear_solid_queue_finished_jobs` comes with Rails: it deletes the records of finished jobs every hour. Write schedules in plain English, such as `at 5am every day`, or as cron expressions; times are in UTC. Commit and push the changes; the scheduler reads the file when it starts, so the schedule applies with the next deployment.

With more than one instance of the worker, or of the web process with Solid Queue inside Puma, each runs a scheduler, and Solid Queue still queues each task once per scheduled time. For a command that isn't a job, you can also use a [cron job](https://docs.sevalla.com/applications/processes.md#cron-jobs), for example `bin/rails runner "Cart.expired.destroy_all"`.

## Verify

1. Click the **Web terminal** tab, and in the shell that opens in the **Web** process, queue a job:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   bin/rails runner "OrderConfirmationJob.perform_later(42)"
   ```

   The command prints `Enqueued OrderConfirmationJob (Job ID: ...) to SolidQueue(default) with arguments: 42`.
2. Click the **Logs** tab and look at the runtime logs of the `Jobs` process, or of the web process with Solid Queue inside Puma. They show `Performing OrderConfirmationJob`, the line `Sending the confirmation for order 42`, and `Performed OrderConfirmationJob (Job ID: ...) from SolidQueue(default) in 3.24ms`.
3. Every 5 minutes, the same logs show `Enqueued InventoryReportJob` and a line such as `0 products in the catalog`.

### Check from a terminal

These checks need no dashboard. `API`, `H`, `APP_ID` and `WEB_ID` are set as in [Deploy with the API](#deploy-with-the-api), and `DEPLOYMENT_ID` is the deployment it started.

1. The rollout's results in the build log, from [Get application deployment logs](https://api-docs.sevalla.com/v3/applications/get-application-deployment-logs) (`getApplicationDeploymentLogsV3`). The messages carry terminal color codes, which `gsub` removes, and `select` drops the lines of the image build, which start with `#`:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   curl -s "$API/applications/$APP_ID/deployments/$DEPLOYMENT_ID/logs?search=has%20been" -H "$H" \
     | jq -r '.[].message | gsub("\u001b\\[[0-9;]*m"; "") | select(startswith("#") | not)'
   ```

   ```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   ✅ Migrate has been successful
   🚀 Web has been deployed successfully.
   🚀 Jobs has been deployed successfully.
   ```

2. Queue a job in the web process with [Run a command in a process](https://api-docs.sevalla.com/v3/applications/run-a-command-in-a-process) (`execProcessCommandV3`), the API's counterpart of the web terminal. The command runs without a shell, and the key needs write access to the application. Rails takes a few seconds to boot, so allow 60:

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   curl -s -X POST "$API/applications/$APP_ID/processes/$WEB_ID/exec" -H "$H" -H "Content-Type: application/json" \
     -d '{"command": ["bin/rails", "runner", "OrderConfirmationJob.perform_later(42)"], "timeout": 60}' \
     | jq -r .stdout
   ```

   ```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   [ActiveJob] Enqueued OrderConfirmationJob (Job ID: b8918c7a-4554-4ebf-b9c0-cdbc2f97bbc2) to SolidQueue(default) with arguments: 42
   ```

3. A few seconds later, the job's lines in the `Jobs` worker's logs, from [Get application runtime logs](https://api-docs.sevalla.com/v3/applications/get-application-runtime-logs) (`getApplicationRuntimeLogsV3`):

   ```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   curl -s "$API/applications/$APP_ID/runtime-logs?search=b8918c7a-4554-4ebf-b9c0-cdbc2f97bbc2" -H "$H" | jq -r '.[].message'
   ```

   Use the job ID from the previous step. The lines start with `[ActiveJob] [OrderConfirmationJob]` and the job ID:

   ```text theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
   [ActiveJob] [OrderConfirmationJob] [b8918c7a-4554-4ebf-b9c0-cdbc2f97bbc2] Performing OrderConfirmationJob (Job ID: b8918c7a-4554-4ebf-b9c0-cdbc2f97bbc2) from SolidQueue(default) enqueued at 2026-10-30T05:35:48.817022311Z with arguments: 42
   [ActiveJob] [OrderConfirmationJob] [b8918c7a-4554-4ebf-b9c0-cdbc2f97bbc2] Sending the confirmation for order 42
   [ActiveJob] [OrderConfirmationJob] [b8918c7a-4554-4ebf-b9c0-cdbc2f97bbc2] Performed OrderConfirmationJob (Job ID: b8918c7a-4554-4ebf-b9c0-cdbc2f97bbc2) from SolidQueue(default) in 69.5ms
   ```

CLI: `sevalla apps processes exec $WEB_ID --app-id $APP_ID --timeout 60 -- bin/rails runner "OrderConfirmationJob.perform_later(42)"`, then `sevalla apps logs runtime $APP_ID --search "order 42"`. MCP: `sevalla_api_update` with `execProcessCommandV3`, and `search_runtime_logs`.

## Running jobs during a deployment

Each deployment starts new instances of the worker, then sends `SIGTERM` to the old ones. Solid Queue stops taking jobs and waits up to 5 seconds (`SolidQueue.shutdown_timeout`) for the running ones to finish. A job that doesn't finish in that time is stopped and returned to the queue: the logs of the old instance show `Release claimed jobs (...)  size: 1`, and a new instance runs the job again from the start. The same happens with Solid Queue inside Puma.

So write jobs that can run twice without harm: check whether the work is already done before you do it, and keep long work in small jobs.

## Deploy with a Dockerfile

The Dockerfile from [Deploy with a Dockerfile](https://docs.sevalla.com/guides/ruby/rails/deploy.md#deploy-with-a-dockerfile) needs no change: `bin/jobs` is part of the image, and the start command runs in its `/rails` folder as the `rails` user. With the Dockerfile, `exec bin/jobs` makes Solid Queue's supervisor the container's main process in the same way, and Solid Queue inside Puma runs under Puma, which is the main process already.

## Troubleshoot

| Symptom | Cause | Fix |
| - | - | - |
| After a deployment, a job fails with `SolidQueue::Processes::ProcessPrunedError` | The start command is `bin/jobs` without `exec`, so the old instance was killed with the job running. | Set the worker's start command to `exec bin/jobs`, then deploy. |
| Jobs stay queued and nothing runs them | No process runs Solid Queue: the `Jobs` worker has no instances, or `SOLID_QUEUE_IN_PUMA` isn't set. | Create the worker, or add the variable, then deploy. |
| The web instance restarts when you run `bin/rails` in the web terminal, or the command exits with code `137` | Solid Queue runs inside Puma on **H1**, which leaves no memory for another Rails process. | Choose **S1** or larger for the web process, or run Solid Queue in a background worker. |

## Deploy with the API

The same steps as [Run the jobs in a background worker](#run-the-jobs-in-a-background-worker), as calls to the [Sevalla API](https://docs.sevalla.com/developer-tools/api.md). `APP_ID` and `H1_ID` are the application and the **H1** size from [Deploy with the API](https://docs.sevalla.com/guides/ruby/rails/deploy.md#deploy-with-the-api) in the deploy guide, with `API` and `H` set as there. Push the jobs and `config/recurring.yml` first: the deployment builds the branch as it is.

**1. Create the background worker**

[Create process](https://api-docs.sevalla.com/v3/applications/create-process) (`createProcessV3`) with `type` `worker` needs the size and a `scaling_strategy`. Keep `exec` in the start command:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
curl -s -X POST "$API/applications/$APP_ID/processes" -H "$H" -H "Content-Type: application/json" \
  -d "$(jq -n --arg size "$H1_ID" \
    '{display_name: "Jobs", type: "worker", entrypoint: "exec bin/jobs", resource_type_id: $size,
      scaling_strategy: {type: "manual", config: {instanceCount: 1}}}')" \
  | jq -c '{display_name, entrypoint}'
```

To run Solid Queue inside Puma instead, skip this call: add `SOLID_QUEUE_IN_PUMA` with the value `1` with [Create environment variable](https://api-docs.sevalla.com/v3/applications/create-environment-variable), and set the web process to **S1** with [Update process](https://api-docs.sevalla.com/v3/applications/update-process). CLI: `sevalla apps processes create $APP_ID --name Jobs --type worker --resource-type $H1_ID --command "exec bin/jobs"`. MCP: `sevalla_api_create` with `createProcessV3` and the same body.

**2. Deploy**

[Trigger deployment](https://api-docs.sevalla.com/v3/applications/trigger-deployment) (`triggerDeploymentV3`):

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
DEPLOYMENT_ID=$(curl -s -X POST "$API/applications/$APP_ID/deployments" -H "$H" -H "Content-Type: application/json" \
  -d '{}' | jq -r .id)
```

CLI: `sevalla apps deployments trigger $APP_ID`. MCP: `trigger_deployment`.

**3. Wait for the deployment**

Poll [Get deployment](https://api-docs.sevalla.com/v3/applications/get-deployment) (`getDeploymentV3`) until the status is final: `success`, `failed`, `cancelled` or `skipped`. The loop keeps polling through a failed request and stops after 30 minutes:

```bash theme={"theme":{"light":"github-dark-default","dark":"github-dark-default"}}
for i in $(seq 1 120); do
  STATUS=$(curl -s "$API/applications/$APP_ID/deployments/$DEPLOYMENT_ID" -H "$H" | jq -r '.status // empty')
  case $STATUS in success|failed|cancelled|skipped) break ;; esac
  sleep 15
done
echo "$STATUS"
```

CLI: `sevalla apps deployments get $DEPLOYMENT_ID --app-id $APP_ID`. MCP: `get_deployment`.

Then run the checks in [Check from a terminal](#check-from-a-terminal).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.