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
- A Django project deployed on Sevalla, as in Deploy Django.
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 updaterequirements.txt:
settings.py:
storefront/celery.py
storefront/__init__.py
storefront/settings.py. Settings that start with CELERY_ configure Celery:
storefront/settings.py
tasks.py module in every installed app. Create an orders app:
INSTALLED_APPS in storefront/settings.py:
storefront/settings.py
orders/tasks.py
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 Choose a Resource and click Create process.
Celery worker as the Name and this Custom start command:2
Deploy
Click Deploy in the undeployed changes bar at the bottom of the page.
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.


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.
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
-
Click the Web terminal tab, and in the shell that opens in the Web process, queue a task:
-
Click the Logs tab and look at the worker’s runtime logs. They show
Task orders.tasks.send_order_confirmation[...] received, the lineSending the confirmation for order 42, andTask orders.tasks.send_order_confirmation[...] succeeded.
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:
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, withAPI, 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 (CLI:
listDatabaseVersionsV3) returns the versions the location offers, oldest first. Save the newest: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 (CLI:
createDatabaseV3). You choose the password, and the API never returns it, so keep it for the connection URL: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 (CLI:
getDatabaseV3) shows status ready when the database is up, with its internal hostname and port:sevalla databases get "$REDIS_ID". MCP: get_database.4
Connect the database
Create internal connection (The API adds no environment variables; the next step adds them. CLI:
createApplicationInternalConnectionV3):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 (CLI:
createEnvironmentVariableV3), once per variable, with the names and format of the dashboard’s internal connection: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 (CLI:
createProcessV3) with type worker, the size and a scaling_strategy. Keep exec at the start of the command: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 (CLI:
createProcessV3) with type cron and the schedule, in UTC unless you add a time_zone: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 (CLI:
triggerDeploymentV3) starts the new processes. With Auto-deploy on, the push already started a deployment, without them; this one queues behind it:sevalla apps deployments trigger "$APP_ID". MCP: trigger_deployment.9
Wait for the deployment
Get deployment (CLI:
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:sevalla apps deployments get "$DEPLOYMENT_ID" --app-id "$APP_ID". MCP: get_deployment.