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.
Image by Annie Ruygt

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.

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

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. To efficiently serve an asynchronous app and avoid unnecessary context-switching that causes a performance penalty, we need an ASGI server (Asynchronous Server Gateway Interface). 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 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 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 is a pure-Python hybrid ASGI/WSGI server that supports HTTP/1, HTTP/2, HTTP/3 (optionally using 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:
    • 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 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 is a newish option in ASGI servers world. It is a Rust ๐Ÿฆ€ hybrid ASGI/WSGI/RSGI 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.

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! โ†’