amanks.dev
  • Work
  • Blog
  • Coming SoonFrontend Minis

  • Work
  • Blog
  • Coming SoonFrontend Minis

All rights reserved © 2025 Aman K. Singh

Deploying Django + uv + sqlite to Fly.io

December 6, 2024

Deploying Django + uv + sqlite to Fly.io

I was working on a Django template DHAT Stack and wanted to deploy it, I chose fly.io because of its awesome cli and straight forward deployment process. Though the process to deploy to fly.io is fairly easy, there were some configurations needed to make it production ready. On top of that, I wanted to use uv to manage the python environment and sqlite for the database as the project was a template and didn't require a full fledged Postgres.

I'll divide this blog into two sections, first we will see the configuration needed to make the Django app production ready and then we will see how to deploy it to fly.io.

Making the Django app production ready

Since I'm using uv to manage the python environment, and you're reading this blog, I'm assuming you're using uv as well and have a pyproject.toml and uv.lock file in the root of your project.

These files will be used in the fly.io build process to install the dependencies and that's the advantage of using uv that we don't need requirements.txt which is not easy to manage.

Environment variables

I'm using django-environ to manage the environment variables and have a .env file in the root of the project which is used to set the environment variables.

Create a .env file in the root of the project and add the following:

Then in the settings.py file, import the environment variables:

Though here we're reading secret key from the .env file, we will soon update it to a different method to take the secret key from fly.io secrets and use a generated secret key in development to not get an error during build process as we're going to put .env file in the gitignore and dockerignore.

Gunicorn

Django comes with a built in minimal server which is not recommended for production, we will use gunicorn to serve the app in production. Gunicorn is a Python WSGI HTTP Server for UNIX. It's a pre-fork worker model ported from Ruby's Unicorn project.

Let's install gunicorn:

That's it, we will later see how to use gunicorn in the fly.io build process.

Static files

We will use whitenoise to serve the static files. Whitenoise is a Django middleware that serves static files in production.

Let's install whitenoise:

Now we need to update the settings.py file to use whitenoise:

  • Add whitenoise to the middleware after django.middleware.security.SecurityMiddleware
  • Set STATICFILES_STORAGE to whitenoise.storage.CompressedManifestStaticFilesStorage
  • Set STATIC_ROOT to the path where the static files will be collected, I'm using BASE_DIR / 'staticfiles'

(Optional but recommended) Use whitenoise in development to keep the behavior of the static files the same in development and production, to do this we just need to add whitenoise.runserver_nostatic to INSTALLED_APPS before django.contrib.staticfiles.

Other configurations

Update ALLOWED_HOSTS to a list of domains that our django app will serve, in our case it will be the domain of the fly app.

I'm keeping the name of our fly app as dhat-stack.

Django also has a setting CSRF_TRUSTED_ORIGINS which is used to trust the origins of the requests, we need to add the domain of the fly app to this list.

Database

For now, let's add the following to the settings.py file:

This will make the database use the DATABASE_URL environment variable if it's available, otherwise it will use sqlite as default.

Deploying to fly.io

Since we're using sqlite, there are some extra steps we need to take for it to work properly in fly.io. To avoid complexity of managing multiple sqlite databases and keeping them in sync accross multiple servers, I'll be deloying my app to only 1 server and using fly.io volumes to persist the database. For small apps this is more than enough.

If you're planning to scale your app to multiple servers, you should use a database that is designed to be scalable like Postgres and a managed databse service like Supabase works great with fly. If you don't want to use a managed database service, fly also has its own semi-managed (or not) postgres database called Fly Postgres.

There might be ways to make sqlite work with multiple servers but I didn't have the need for it, fly also has its own database service called LiteFS, I needed something simple and sqlite worked fine for my use case.

Creating a fly app

Note: You need to have the fly cli installed to be able to do this. Install flyctl

First we need to create a fly app, we can do this by running the following command:

This will create a fly app without deploying it and without high availability (ha) i.e only 1 server.

Creating a volume

To store the sqlite database file even after the app is stopped, we need to create a volume.

I followed the fly.io volumes guide to launch a new fly app with a volume. For an existing fly app, a flyctl command will be needed, you can follow this guide to create a volume for an existing app.

Since I've already created a fly app, the next step is to create a volume.

Now we need to tell fly to mount this volume during the deployment process, we can do this by updating the fly.toml file.

Now deploy:

Deployment might fail but volumes are created in the background so we can ignore that.

To manually create a volume, you can run the following command:

This will create a volume in the iad region with a size of 1GB.

Secrets

Now create secrets for the environment variables we added to the .env file.

We don't need to set a secret key as fly will create it for us.

Dockerfile

If you're using uv, its likely that the Dockerfile generated by flyctl contains poetry config as it detects pyproject.toml and assumes it's a poetry project.

Since uv is new we will have to update the Dockerfile to use uv instead of poetry.

This is the complete Dockerfile I used:

startup.sh is a simple script that is needed for migrations to work properly. Create it in the root of the project and add the following:

Finally, deploy the app to fly.io:

Conclusion

This guide covered the steps I took to deploy a Django app to fly.io using uv, sqlite and a custom Dockerfile. I hope this guide will help you deploy your Django app to fly.io.

You can find the complete code for the Django app in the DHAT Stack repository.

If you found this guide helpful, please consider giving DHAT Stack repo a star and sharing this blog on bsky or twitter.

Acknowledgements

  • DjangoCon Europe 2023 - Using sqlite in production
  • Simon Willison Blog - Datasette and Gunicorn
  • Using SQLite for a Django application on fly.io by Andrew Mshar
  • Django beats by fly.io

Tags

djangofly.iouvsqlitepythondocker

Article Info

👁️ ... views

⏱️ 7 min read

# .env
SECRET_KEY=your_secret_key
DEBUG=False
fly.toml
[mounts]
source = "dhat-stack-db"
destination = "/data"
# Dockerfile
ARG PYTHON_VERSION=3.12-slim

FROM python:${PYTHON_VERSION}

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN mkdir -p /code

WORKDIR /code

RUN pip install uv
COPY pyproject.toml uv.lock /code/
RUN uv venv
RUN uv sync
COPY . /code
RUN apt-get update && apt-get install -y \
sqlite3 \
&& rm -rf /var/lib/apt/lists/*
RUN uv run manage.py collectstatic --noinput
RUN chmod +x startup.sh

EXPOSE 8000
ENTRYPOINT ["./startup.sh"]
settings.py
import environ

env = environ.Env(
# set casting, default value
DEBUG=(bool, False),
)

# Take environment variables from .env file
environ.Env.read_env(BASE_DIR / '.env')

DEBUG = env('DEBUG')

SECRET_KEY = env('SECRET_KEY')
settings.py
INSTALLED_APPS = [
...
'whitenoise.runserver_nostatic',
'django.contrib.staticfiles',
...
]

MIDDLEWARE = [
...
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
...
]

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

STATIC_ROOT = BASE_DIR / 'staticfiles'
settings.py
ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'dhat-stack.fly.dev']
settings.py
CSRF_TRUSTED_ORIGINS = ['https://dhat-stack.fly.dev']
settings.py
DATABASES = {
'default': env.db('DATABASE_URL', default='sqlite:///db.sqlite3'),
}
uv add gunicorn==20.1.0
uv add whitenoise==6.5.0
fly launch --no-deploy --ha=false
fly deploy --ha=false
fly volumes create dhat-stack-db --region iad --size 1
fly secrets set DATABASE_URL=sqlite:////data/db.sqlite3
startup.sh
#!/bin/bash
uv run manage.py migrate
sqlite3 /data/db.sqlite3 'PRAGMA journal_mode=WAL;'
sqlite3 /data/db.sqlite3 'PRAGMA synchronous=1;'
uv run gunicorn --bind :8000 --workers 2 dhat_stack.wsgi
fly deploy --ha=false