async def views under any server, but under a WSGI server, such as the Gunicorn that Ingot starts by default, each async view runs in a one-off event loop, without the concurrency it’s written for. Served over ASGI, async views run on an event loop. On Sevalla, you install Uvicorn and name the project’s ASGI application in the settings, and Ingot starts Uvicorn on asgi.py instead of Gunicorn on wsgi.py.
At a glance
This guide changes one thing in the application from Deploy a Django application:Environment variables
This guide adds no environment variables.WEB_CONCURRENCY sets the number of Uvicorn worker processes, as it does for Gunicorn; the deploy guide lists the other variables.
Before you begin
- A Django project deployed on Sevalla, as in Deploy Django.
Install Uvicorn
Install Uvicorn, the ASGI server, and updaterequirements.txt:
Add an async view
Add a view that awaits, and that reports which kind of request object Django built:storefront/views.py
storefront/urls.py
await Product.objects.aget(pk=1) and async for product in Product.objects.all(), or wrap synchronous code in asgiref.sync.sync_to_async.
Serve the project over ASGI
Instorefront/settings.py, add ASGI_APPLICATION under WSGI_APPLICATION:
storefront/settings.py
ASGI_APPLICATION and Uvicorn is installed, Ingot starts Uvicorn on that application instead:
PORT environment variable, 8080 by default. Uvicorn runs 1 worker process. On a larger size, set the WEB_CONCURRENCY environment variable to the number of worker processes, for example 2: Uvicorn reads it at start. If you set a Custom start command on the web process, it overrides what Ingot detects: clear it on the Processes tab (click ⋯ on the Web process, then click Update process). See Set the start command.
If the application builds from your own Dockerfile, the Dockerfile’s CMD starts the server, not Ingot: replace the Gunicorn CMD with Uvicorn. exec replaces the shell with Uvicorn, which then handles SIGTERM itself:
Dockerfile
requirements.txt included. If Auto-deploy is off, click Deploy now on the application’s Overview tab.
Verify
-
When the deployment’s status is Deployed, open it on the Deployments tab. The build plan’s Start row reads
uvicorn storefront.asgi:application --host 0.0.0.0 --port "$PORT". With a Dockerfile, the build log has no build plan: steps 2 and 3 show that Uvicorn serves the project. -
Request the view, with your application’s domain:
The response is
{"interface": "ASGIRequest"}. Under Gunicorn, the same view returns{"interface": "WSGIRequest"}. -
The web process’s runtime logs, on the application’s Logs tab, include
Uvicorn running on http://0.0.0.0:8080. Uvicorn also logsASGI 'lifespan' protocol appears unsupported.: Django doesn’t implement ASGI lifespan events, and the line is harmless. See Logs.
getApplicationDeploymentLogsV3) finds the build plan’s Start row, and Get application runtime logs (getApplicationRuntimeLogsV3) finds Uvicorn’s line. APP_ID comes from Deploy with the API and DEPLOYMENT_ID from Deploy with the API:
sevalla apps deployments logs "$DEPLOYMENT_ID" --app-id "$APP_ID" --search "uvicorn storefront" and sevalla apps logs runtime "$APP_ID" --search "Uvicorn running". MCP: get_deployment_logs and search_runtime_logs.
Deploy with the API
The application from Deploy with the API needs no new settings: Ingot picks Uvicorn from the code. Push the changes, then deploy in the same shell, withAPI and APP_ID set. Each step names the CLI command and the Sevalla MCP server tool that do the same.
1
Deploy
Trigger deployment (CLI:
triggerDeploymentV3) deploys the latest commit. With Auto-deploy on, the push already started a deployment of the same commit; this one queues behind it, and you can wait for either:sevalla apps deployments trigger "$APP_ID". MCP: trigger_deployment.2
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.