Skip to main content
A background task is work that runs outside the request, such as sending email or resizing images. On Sevalla, Celery runs the tasks in a background worker process, which runs from the same image as the web process with its own start command, and a Redis database holds the queue between them. Django 6.0 added a tasks framework (django.tasks) for defining and enqueuing tasks, but Django doesn’t run them: its built-in backends are for development and testing only. Celery provides both the queue and the worker.

At a glance

This guide adds to the application from Deploy a Django application:

Environment variables

The internal connection also adds REDIS_HOST, REDIS_PORT and REDIS_PASSWORD, which the project doesn’t read. The deploy guide lists the other variables.

Before you begin

Create a Redis database

1

Create the database

In the sidebar, click Databases, then click Create database. Under Database, choose Redis, or Valkey: the code in this guide works with both. Enter storefront-redis as the Display name, choose the same Data center as the application and a Size, then click Create database.
2

Connect it to the application

In the sidebar, click Applications, then click the application. Click the Networking tab. Under Connected services, click Add internal connection. Choose storefront-redis as the Service, turn on Add environment variables to the application, and click Add internal connection. Sevalla adds REDIS_HOST, REDIS_PORT, REDIS_PASSWORD and REDIS_URL. See Connect an application.

Add Celery to the project

Install Celery 5.6 with its Redis support, and update requirements.txt:
Create the Celery application next to settings.py:
storefront/celery.py
Load it when Django starts:
storefront/__init__.py
Point Celery at Redis in storefront/settings.py. Settings that start with CELERY_ configure Celery:
storefront/settings.py
Celery finds tasks in a tasks.py module in every installed app. Create an orders app:
Add it to INSTALLED_APPS in storefront/settings.py:
storefront/settings.py
Then add a task to it:
orders/tasks.py
Queue it from a view or anywhere else with send_order_confirmation.delay(order.id). Commit and push the changes, requirements.txt included: Ingot installs Celery on the next build.

Run the worker on Sevalla

1

Create the background worker

On the application’s Processes tab, click Create process. Choose Background worker, enter Celery worker as the Name and this Custom start command:
Choose a Resource and click Create process.
2

Deploy

Click Deploy in the undeployed changes bar at the bottom of the page.
Start the command with exec. Sevalla runs a custom start command with /bin/sh -l -c, and the shell, as the instance’s main process, doesn’t pass SIGTERM on. exec replaces the shell with Celery, so when an instance stops during a deployment, Celery gets the signal, finishes the tasks it’s running and logs worker: Warm shutdown (MainProcess). Without exec, Sevalla stops the worker 30 seconds later, in the middle of its tasks. The worker runs from the application’s image, so the same start command works when the application builds from your own Dockerfile: Celery is installed with the other packages, on the PATH of the shell that runs the command. Set --concurrency to match the size. Without it, Celery starts one worker process per CPU of the machine it runs on, which can be more than the process’s memory holds. Two processes fit in H1 (0.3 GB of memory). To process more tasks at once, add instances in the process’s Scaling section, or choose a larger size and raise --concurrency. See Scaling.
The Create process sheet with Background worker selected, the Name and Custom start command fields, the resource and the scaling settingsThe Create process sheet with Background worker selected, the Name and Custom start command fields, the resource and the scaling settings

Run tasks on a schedule

Run scheduled work as a cron job: Sevalla starts a new instance at each scheduled time, runs the command, and stops it. Put the work in a management command, then create the process:
1

Create the cron job

On the application’s Processes tab, click Create process. Choose Cron job, enter Clear sessions as the Name, python manage.py clearsessions as the Custom start command, and 0 3 * * * as the Schedule (every day at 03:00 UTC). Click Create process.
2

Deploy

Click Deploy in the undeployed changes bar at the bottom of the page.
Schedules run in UTC unless you turn on Set custom time zone. To keep the schedule in Celery instead, run Celery beat in a second background worker with the start command exec celery -A storefront beat --loglevel INFO --schedule /tmp/celerybeat-schedule, and keep that process at 1 instance: each beat instance sends every scheduled task. Beat records when each task last ran in a schedule file, by default celerybeat-schedule in the working directory. /tmp is writable both in Ingot’s image and in the image from your own Dockerfile, which keeps /app read-only: there, beat without --schedule stops with PermissionError: [Errno 13] Permission denied: 'celerybeat-schedule'.

Verify

  1. Click the Web terminal tab, and in the shell that opens in the Web process, queue a task:
  2. Click the Logs tab and look at the worker’s runtime logs. They show Task orders.tasks.send_order_confirmation[...] received, the line Sending the confirmation for order 42, and Task orders.tasks.send_order_confirmation[...] succeeded.
A script or an AI agent queues the task with Run a command in a process (execProcessCommandV3) in the web process, then finds the worker’s line with Get application runtime logs (getApplicationRuntimeLogsV3). APP_ID and WEB_ID come from Deploy with the API:
Celery logs a task’s print output at the WARNING level. If the task doesn’t arrive, read the worker’s runtime logs. Cannot connect to redis://localhost:6379/0 means REDIS_URL isn’t set: Celery falls back to the local default from the settings and retries every few seconds. Check the internal connection on the Networking tab, then deploy.

Deploy with the API

This sequence adds the Redis database, the worker and the cron job to the application from Deploy with the API and continues in its shell, with API, CLUSTER_ID, H1_ID, DB1_ID and APP_ID set. Push the code changes from Add Celery to the project first. Each step names the CLI command and the Sevalla MCP server tool that do the same.
1

Look up the Redis version

List database versions (listDatabaseVersionsV3) returns the versions the location offers, oldest first. Save the newest:
CLI: sevalla resources db-versions --cluster "$CLUSTER_ID" --type redis. MCP: not needed, create_database takes the newest version by default.
2

Create the database

Create database (createDatabaseV3). You choose the password, and the API never returns it, so keep it for the connection URL:
CLI: sevalla databases create --name storefront-redis --type redis --db-version "$REDIS_VERSION" --cluster "$CLUSTER_ID" --resource-type "$DB1_ID" --db-name storefront --db-password "$REDIS_PASSWORD". MCP: create_database with type: "redis"; with connect_to_application_id and env_var_name: "REDIS_URL", it also does the internal connection and adds REDIS_URL.
3

Wait for the database

Get database (getDatabaseV3) shows status ready when the database is up, with its internal hostname and port:
CLI: sevalla databases get "$REDIS_ID". MCP: get_database.
4

Connect the database

Create internal connection (createApplicationInternalConnectionV3):
The API adds no environment variables; the next step adds them. CLI: sevalla apps internal-connections create "$APP_ID" --target-id "$REDIS_ID" --target-type database. MCP: sevalla_api_create with createApplicationInternalConnectionV3 and the same body.
5

Add the environment variables

Create environment variable (createEnvironmentVariableV3), once per variable, with the names and format of the dashboard’s internal connection:
CLI: sevalla apps env-vars create "$APP_ID" --key REDIS_URL --value "redis://:$REDIS_PASSWORD@$REDIS_HOST:$REDIS_PORT/0", once per variable. MCP: set_environment_variables.
6

Create the background worker

Create process (createProcessV3) with type worker, the size and a scaling_strategy. Keep exec at the start of the command:
CLI: sevalla apps processes create "$APP_ID" --name "Celery worker" --type worker --resource-type "$H1_ID" --command "exec celery -A storefront worker --loglevel INFO --concurrency 2". MCP: sevalla_api_create with createProcessV3 and the same body.
7

Create the cron job

Create process (createProcessV3) with type cron and the schedule, in UTC unless you add a time_zone:
CLI: sevalla apps processes create "$APP_ID" --name "Clear sessions" --type cron --resource-type "$H1_ID" --command "python manage.py clearsessions" --schedule "0 3 * * *". MCP: sevalla_api_create with createProcessV3 and the same body.
8

Deploy

Trigger deployment (triggerDeploymentV3) starts the new processes. With Auto-deploy on, the push already started a deployment, without them; this one queues behind it:
CLI: sevalla apps deployments trigger "$APP_ID". MCP: trigger_deployment.
9

Wait for the deployment

Get deployment (getDeploymentV3) reports the status, which ends as success, failed, cancelled or skipped. The loop keeps polling through a failed request, for up to 30 minutes:
CLI: sevalla apps deployments get "$DEPLOYMENT_ID" --app-id "$APP_ID". MCP: get_deployment.
Then run the checks in Verify.