---
title: "ASGI deployment options for Django"
---

# ASGI deployment options for Django

![A pony with its tongue sticking out chooses as ice cream flavor from a glass display counter containing 6 buckets of ice cream.](https://fly.io/django-beats/asgi-deployment-options-for-django/assets/asgi_deployment_for_django-cover.webp)

[Annie Ruygt](https://annieruygtillustration.com/)

Mariusz Felisiak, a Django and Python contributor and a Django Fellow, explores the world of ASGI servers. Django on Fly.io is pretty sweet! Check it out: [you can be up and running on Fly.io in just minutes.](https://fly.io/docs/django/)

Asynchronous support in Django is constantly improving and each version introduces new features. With already implemented:

- asynchronous [views](https://docs.djangoproject.com/en/stable/topics/async/#async-views) and [middlewares](https://docs.djangoproject.com/en/stable/topics/http/middleware/#asynchronous-support),
- asynchronous [ORM interface](https://docs.djangoproject.com/en/stable/topics/async/#queries-the-orm),
- asynchronous support for [decorators](https://docs.djangoproject.com/en/stable/topics/async/#decorators) and signal dispatchers,

and growing support in `contrib` packages (like `auth` or `sessions`), it’s now possible to create a fully-asynchronous real world application in Django. You can find more details on this topic in one of my [previous blog posts](https://fly.io/django-beats/running-tasks-concurrently-in-django-asynchronous-views/). To efficiently serve an asynchronous app and avoid unnecessary context-switching that causes a [performance penalty](https://docs.djangoproject.com/en/stable/topics/async/#performance), we need an [ASGI](https://asgi.readthedocs.io/) server ( ***A*** *synchronous* ***S*** *erver* ***G*** *ateway* ***I*** *nterface* ). This article explores the most popular ASGI web servers and how to integrate them in our development and deployment processes.

Let’s start with a brief introduction of the ASGI `application`.

## ASGI application

The `application` object is used by the ASGI-compatible web servers to communicate with your project. ASGI `application` is defined in the `asgi.py` file generated by the `startproject` management command in the project root directory. This works analogously to the WSGI `application` defined for WSGI-compatible web servers in the `wsgi.py`. This is what the `hello_django` project structure looks like with two entry-points for web servers:

```
hello-django/
    manage.py
    hello_django/
        __init__.py
        settings.py
        urls.py
        asgi.py  ← Entry-point for ASGI-compatible web servers.
        wsgi.py  ← Entry-point for WSGI-compatible web servers.
```

Most ASGI web servers accept path to the `application` callable, so we will pass it as: `<project name>.asgi:application`, e.g. `hello_django.asgi:application`.

Now let’s dive into the most popular ASGI web servers 🤿

## Daphne

[Daphne](https://pypi.org/project/daphne/) is a pure-Python ASGI server that supports HTTP/1, HTTP/2, and WebSockets. It’s written and maintained under the wing of the Django organization which is a big plus from a long-term support perspective. It should be maintained as long as Django exists.

Another advantage is that it provides integration with the built-in `runserver` command, which allows you to run your project [under ASGI during development](https://docs.djangoproject.com/en/dev/howto/deployment/asgi/daphne/#integration-with-runserver) and benefit from all `runserver` functionalities, such as system checks, auto-reloader, or readable logs.

To enable `runserver` integration, add `daphne` to the `INSTALLED_APPS` and define `ASGI_APPLICATION` with the path to your ASGI `application` in your settings file. For example:

```
# hello_world/settings.py

INSTALLED_APPS = [
    "daphne",  # ← Added.
    ...,
]

ASGI_APPLICATION = "hello_django.asgi.application"  # ← Added.
```

Now `runserver` will use Daphne development server:

```
python manage.py runserver
```

```
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

March 19, 2024 - 10:18:47
Django version 5.0.3, using settings 'hello_django.settings'
Starting ASGI/Daphne version 4.1.0 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

To deploy on ASGI using Daphne, first make sure that it’s installed and listed in `requirements.txt`:

```
python -m pip install Daphne
```

```
pip freeze > requirements.txt
```

Second, update `Dockerfile` generated by `fly launch` to use Daphne instead of Gunicorn:

```
# Dockerfile
...

EXPOSE 8000

# ↓↓ Update to Daphne ↓↓
CMD ["daphne", "-b", "0.0.0.0", "-p", "8000", "hello_django.asgi:application"]
```

## Hypercorn

[Hypercorn](https://pgjones.gitlab.io/hypercorn/) is a pure-Python hybrid ASGI/WSGI server that supports HTTP/1, HTTP/2, HTTP/3 (optionally using [`aioquic`](https://github.com/aiortc/aioquic/) library), and WebSockets. It’s really flexible and can be used both during development and as a production web server. It also has a medley of configuration options, including:

- `--worker-class`: the type of worker to use, with support for various [worker classes](https://hypercorn.readthedocs.io/en/latest/discussion/workers.html#workers):
  - `asyncio` - default
  - `uvloop` - available when installed with `hypercorn[uvloop]`
  - `trio` - available when installed with `hypercorn[trio]`
- `--reload`: enables auto-reload on code changes, which is very handy for local development.

To deploy on ASGI using Hypercorn, first make sure that it’s installed and listed in `requirements.txt`:

```
python -m pip install hypercorn
```

```
pip freeze > requirements.txt
```

Second, update `Dockerfile` generated by `fly launch` to use Hypercorn instead of Gunicorn:

```
# Dockerfile
...

EXPOSE 8000

# ↓↓ Update to Hypercorn ↓↓
CMD ["hypercorn", "--bind", ":8000", "--workers", "2", "hello_django.asgi:application"]
```

## Uvicorn

[Uvicorn](https://www.uvicorn.org/) is a pure-Python ASGI server based on `uvloop` that supports HTTP/1.1 and WebSockets. Use the `uvicorn` command to run your project under ASGI during development:

```
uvicorn hello_django.asgi:application
```

```
INFO:     Started server process [6591]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```

Passing `--reload` causes the server to reload when Python files are changed. We can pass `--reload-include` to extend this behavior for other files, e.g. HTML templates:

```
uvicorn --reload --reload-include *.py --reload-include *.html hello_django.asgi:application
```

```
INFO:     Will watch for changes in these directories: ['/examples/hello_django']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [6682] using WatchFiles
INFO:     Started server process [6684]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
```

To deploy on ASGI using Uvicorn, first make sure that both Uvicorn and Gunicorn are installed and listed in `requirements.txt`:

```
python -m pip install uvicorn gunicorn
```

```
pip freeze > requirements.txt
```

Second, update `Dockerfile` generated by `fly launch` to start Gunicorn using the Uvicorn worker class:

```
# Dockerfile
...

EXPOSE 8000

# ↓↓ Update to the Uvicorn worker class ↓↓
CMD ["gunicorn", "--bind", ":8000", "--workers", "2", "-k", "uvicorn.workers.UvicornWorker", "hello_django.asgi:application"]
```

Gunicorn is a combat-proven web server that implements many essential features, that’s why it’s recommended for running Uvicorn workers in a production environment.

## Granian

[Granian](https://github.com/emmett-framework/granian) is a newish option in ASGI servers world. It is a Rust 🦀 hybrid ASGI/WSGI/[RSGI](https://github.com/emmett-framework/granian/blob/master/docs/spec/RSGI.md) server that supports HTTP/1, HTTP/2, and WebSockets. It can be used both during development and as a production web server. It has a myriad of configuration options and provides an auto-reloader (when installed with `granian[reload]`) that is useful for development. Use the `granian` command to develop your project under ASGI:

```
granian --reload hello_django.asgi:application
```

```
[INFO] Starting granian (main PID: 6049)
[INFO] Listening at: 127.0.0.1:8000
[INFO] Spawning worker-1 with pid: 6050
[INFO] Started worker-1
[INFO] Started worker-1 runtime-1
```

The only thing I personally miss is the lack of access logs, but this [may change in the future](https://github.com/emmett-framework/granian/issues/152).

To deploy on ASGI using Granian, first make sure that it’s installed and listed in `requirements.txt`:

```
python -m pip install granian
```

```
pip freeze > requirements.txt
```

Second, update `Dockerfile` generated by `fly launch` to use Granian instead of Gunicorn:

```
# Dockerfile
...

EXPOSE 8000

# ↓↓ Update to Granian ↓↓
CMD ["granian", "--interface", "asgi", "--host", "0.0.0.0", "--port", "8000", "--workers", "2", "hello_django.asgi:application"]
```

## Closing thoughts

The world of ASGI servers is truly rich and everyone should find something that suits their personal preferences. This article mentions just some of the most popular and solid options. Choosing an ASGI server for a production environment is like picking an ice cream flavor - everyone has to find their favorite type based on personal taste.

Try them and share!

## Django really flies on Fly.io

You already know Django makes it easier to build better apps. Well now Fly.io makes it easier to deploy those apps and move them closer to your users making it faster for them too!

[Deploy a Django app today! →](https://fly.io/docs/django/)
