Serving Django with Caddy
I’m in the progress of migrating my Django sites from NGINX to Caddy.
Three reasons:
- Caddy automatically handles HTTPS for you. This means no more setting up certbot and making sure NGINX restarts on certificate renewal. The TLS provisioning is robust and even uses ZeroSSL as a fallback option, in case Let’s encrypt doesn’t work.
- The configuration is much more readable.
- It’s fast, modern and written in Go.
Let’s start. I assume you have a machine running and a domain pointing to this machine.
Install caddy#
Follow the instructions at https://caddyserver.com/docs/install.
Instructions for debian
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Caddy Hello World#
/etc/caddy/Caddyfile
:
{
debug
}
yourdomain.com {
respond "ok"
}
Now enable and start the caddy service with:
sudo systemctl enable caddy
sudo systemctl start caddy
And visit your domain in the browser. You should see a secured connection, responding “ok”.
To troubleshoot you can watch journalctl -f -u caddy
in a different session.
Gunicorn#
Install gunicorn with sudo pip3 install gunicorn
.
Now we create a gunicorn configuration in your django project folder:
/path/to/proj/gunicorn.conf.py
:
bind = "localhost:3000"
workers = 4
keepalive = 5
wsgi_app = "my-django-main-app.wsgi" # replace with your main app (contains wsgi.py)
Then create a systemd unit:
/etc/systemd/system/gunicorn.service
:
[Install]
WantedBy=multi-user.target
[Unit]
Description=Gunicorn service
After=network.target
[Service]
WorkingDirectory=/path/to/proj/
ExecStart=/usr/local/bin/gunicorn
Enable and start gunicorn with:
sudo systemctl enable gunicorn
sudo systemctl start gunicorn
I use poetry to manage dependencies, but that is another topic. In this case you would
poetry add gunicorn
in your project and use
ExecStart=/usr/local/bin/poetry run gunicorn
in the systemd unit.
Reverse proxy#
Now we use caddy as a reverse proxy to pass our incoming traffic over to gunicorn.
{
debug
}
yourdomain.com {
encode zstd gzip
handle {
reverse_proxy localhost:3000
}
}
Restart caddy with sudo systemctl restart caddy
and visit your site in the browser.
This serves the page but misses your static files.
Serving static files#
We’ll collect all static files in a folder called staticfiles
.
In your django project, modify your settings.py
:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_DIRS = [
BASE_DIR / "static",
]
MEDIA_ROOT = BASE_DIR / "media"
MEDIA_URL = "/media/"
and the run the collectstatic command:
python manage.py collectstatic
Your static files (even the ones from contrib.admin) should now be copied into the
staticfiles
folder in your project.
So our production-ready Caddyfile
is now:
{
email you@yourdomain.com
}
yourdomain.com {
encode zstd gzip
handle_path /static/* {
file_server {
root "/path/to/proj/staticfiles"
}
}
handle_path /media/* {
file_server {
root "/path/to/proj/media"
}
}
handle {
reverse_proxy localhost:3000
}
}
Restart caddy and your site is online. I know that the Caddyfile could be shortened, but I like it this way.
Thanks for reading!