# Background processes

**Processes → Background processes** manages long-running commands that belong to an app - a queue worker (Celery, ARQ, Dramatiq, RQ, TaskIQ), a scheduler daemon, a consumer, a bot. Each one runs as its own systemd service: supervised, restarted on crash, restarted on every deploy so it always runs your latest code.

(For commands that run and then exit on a schedule - a nightly cleanup, a report - use the [Scheduler](scheduler.md) instead, on the tab next to this one.)

There's no tool lock-in and nothing framework-specific to configure. A process is just a command we keep alive; what it *is* lives in the command, not a setting.

## Adding a process

1. **Processes → Background processes → Add background process.**
2. Give it a **name** and the **command**. Bare tool names (`celery`, `arq`, `rq`, `dramatiq`) resolve in the app's virtualenv, and the working directory is your project folder - so commands read the same as they would over SSH.
3. Optionally set a **graceful shutdown** timeout (see [Safe restarts](#safe-restarts)).

The form offers example commands based on your stack - async apps (FastAPI, or Django on ASGI) get the async-native queues first, sync apps get Celery and RQ first. They're click-to-fill starting points; edit them freely. Deeper per-tool setup lives in each tool's own docs.

## Pointing a worker at Redis

Your server already runs Redis, and most queue tools want a broker. Two ways to hand it over:

- **On the command line** - drop the `$REDIS_URL` token into the command and Pyvolt substitutes the box's Redis (with a per-app database index) when it writes the service. For tools that take the broker as a flag:

  ```
  rq worker --url $REDIS_URL high default
  celery -A myproject worker --broker $REDIS_URL --loglevel info
  ```

- **From your config** - if your tool reads the broker in code (ARQ's `WorkerSettings`, or Celery configured in `settings.py`), a command-line token can't reach it. Instead set a `REDIS_URL` environment variable on the [Environment tab](environment-variables.md) to the value shown in the add-process form, and read it in your app.

The same substitution covers `$SOCK`, `$SOCK_PATH` and `$APP_MODULE`, matching the tokens you can use in a [serve command](deploying-django.md).

## Schedulers and periodic tasks

How you run periodic work depends on what you need:

- **No queue, just run a command on a schedule** (a management command, a cleanup, a report) - use the [Scheduler](scheduler.md), on the tab next to this one. It runs any command on a cron schedule and captures its output; nothing to keep alive.
- **Worker-embedded schedulers** (ARQ, SAQ) run cron inside the worker itself - there's nothing extra to add here; declare the schedule in your code and the worker handles it.
- **Celery beat** is a separate long-running daemon, so it's just another background process. Add one with:

  ```
  celery -A myproject beat --loglevel info
  ```

  Beat must be a singleton - two schedulers double-fire every periodic task - so add exactly one. Each process is a single systemd unit, so one beat process is one scheduler; just don't add a second.

For those code-declared schedulers there's nothing to administer in Pyvolt: the process runs whatever schedule your code declares (Celery's `app.conf.beat_schedule` / `CELERY_BEAT_SCHEDULE`, ARQ's `cron` jobs, and so on). Edit the schedule in your repo and push - deploys restart the process, so changes apply automatically. Only the [Scheduler](scheduler.md) is administered in Pyvolt, where you set the cron expression per job.

## Any command works

Beyond queues, anything long-running is fair game: a message consumer, a websocket bridge, a chat bot, a polling loop. Name it, paste the command, and it becomes a supervised service like the rest.

## Status, honestly

Each row shows the real systemd state - running, stopped, starting, or **crash-loop** with the exit code when a process keeps dying. No guessing about causes: click the log icon to see the process's actual journal output (the dialog opens instantly and loads the last 80 lines).

Check process state from your terminal (the [CLI reference](cli-commands.md#processes-logs) also covers tailing a process's log and restarting one):

```cli
pyvolt ps myapp.com
```

## Safe restarts

Stopping or restarting sends SIGTERM first - for a well-behaved worker that's a warm shutdown (Celery, for instance, finishes in-flight tasks and requeues the rest). The **stop timeout** (default 75s, up to 600s) is how long a process gets before it's force-killed; raise it for workers that chew long tasks.

## Deploys restart everything

A successful deploy restarts the app *and* all its processes - workers never keep running stale code.
