How to Set Up Your Django REST API with uv and Deploy it on Digital Ocean using Docker

Parshva ShahParshva Shah
8 min read

uv is a new, extremely fast Python package and project manager developed by Astral, written in Rust. This comprehensive guide will walk you through using uv with your Django project and deploying it on DigitalOcean using Docker.

You can find the source code for this tutorial here.

First, you will need to install uv on your machine with the following command:

curl -LsSf https://astral.sh/uv/install.sh | sh

Now that you have uv installed on your machine, use the following command to create a Django project with uv:

uv init django-app

After going into the directory, you can see the following files:

Image.png

You can test uv by running the hello.py file with the following command:

Image.png

After testing the hello.py file, you can delete it as we are going to set up Django in the following steps.

To install Python packages with uv, you can use the uv add command. To install Django, make sure you are in your project directory that you created with uv init:

uv add django django-rest-framework django-cors-headers

Now let's get started with setting up a simple Django CRUD project with the following command:

uv run django-admin startproject core .

This will set up the Django project in your existing directory.

Now add restframework and corsheaders in your installed apps and set middleware like this:

INSTALLED_APPS = [
    ...,
    'rest_framework',
    'corsheaders',
]

MIDDLEWARE = [
    ...,
    'django.middleware.common.CommonMiddleware',
    "corsheaders.middleware.CorsMiddleware",
    'django.middleware.csrf.CsrfViewMiddleware',
    ...
]

After this, let's create a new app called products and add it to INSTALLED_APPS as well:

uv run python3 manage.py startapp products

Create a simple Django model for products:

from django.db import models

# Create your models here.
class Product(models.Model):
    name = models.CharField(max_length=255)
    price = models.IntegerField()
    description = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

Run migrations:

uv run python3 manage.py makemigrations
uv run python3 manage.py migrate

Now you can set up serializers, views, and API endpoints in the following way:

# serializers.py

from rest_framework import serializers
from .models import Product

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = '__all__'
# views.py

from django.shortcuts import render
from rest_framework import generics
from .models import Product
from .serializers import ProductSerializer

# Create your views here.
class ProductList(generics.ListCreateAPIView):
    """
    List all products or create a new product
    """
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

class ProductDetail(generics.RetrieveUpdateDestroyAPIView):
    """
    Retrieve, update or delete a product instance
    """
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
# products/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('api/products/', views.ProductList.as_view(), name='product-list'),
    path('api/products/<int:pk>/', views.ProductDetail.as_view(), name='product-detail'),
]

# core/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('products.urls')),
]

Now after running your server with uv run python3 manage.py runserver, you can test your API in the browsable API view or through your preferred method via this URL: http://127.0.0.1:8000/api/products/.

Now that we have our simple REST CRUD API ready, let's move on to setting up Docker for this Django REST API using uv as our package manager.

Let's start with creating a Dockerfile. This Dockerfile creates a Python 3.12 application container using uv with optimized dependency management. It sets up a virtual environment, installs system and Python dependencies in separate layers for better caching, and configures the application to run with Gunicorn web server on port 8000. The Dockerfile also includes configuration with Nginx setup.

Prerequisites:

  • You need to have Docker CLI and Docker Desktop (or Orbstack in my case) installed on your machine.

  • For the env example file, you can find it here.

FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim

WORKDIR /app

ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Install system dependencies
RUN apt-get update && apt-get install -y \
    netcat-traditional \
    libpq-dev \
    libpq5 \
    && rm -rf /var/lib/apt/lists/*

# Copy dependency files first
COPY pyproject.toml uv.lock ./

# Create and activate virtual environment
RUN uv venv

# Install dependencies
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-install-project --no-dev

# Copy the scripts first
COPY scripts/ ./scripts/
RUN chmod +x scripts/entrypoint.sh scripts/start.sh

# Copy the rest of the application
COPY . .

# Install project dependencies
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-dev

Create Docker Compose:

version: '3.8'

services:
  web:
    build: .
    command: sh -c "sh ./scripts/entrypoint.sh && sh ./scripts/start.sh"
    volumes:
      - .:/app
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    expose:
      - 8000
    env_file:
      - .env
    environment:
      - PYTHONUNBUFFERED=1
    restart: unless-stopped

  nginx:
    build: ./nginx
    volumes:
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    ports:
      - "80:80"
    depends_on:
      - web
    restart: unless-stopped

volumes:
  static_volume:
  media_volume:

To get started, run docker-compose up. This Docker configuration allows you to access the REST API we've developed within the Docker setup.

Now let's set up Nginx:

# nginx/Dockerfile

FROM nginx:1.25-alpine

RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/
# nginx/nginx.conf

upstream django_app {
    server web:8000;
}

server {
    listen 80;
    server_name _;
    client_max_body_size 100M;

    location / {
        proxy_pass http://django_app;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }

    location /static/ {
        alias /app/staticfiles/;
    }

    location /media/ {
        alias /app/media/;
    }
}
# scripts/entrypoint.sh

#!/bin/sh

# Exit immediately if a command exits with a non-zero status
set -e

# Run migrations
uv run python manage.py migrate

# Collect static files
uv run python manage.py collectstatic --noinput 

# scripts/start.sh

#!/bin/sh

# Exit immediately if a command exits with a non-zero status
set -e

# Start Gunicorn
uv run gunicorn core.wsgi:application --bind 0.0.0.0:8000 --workers 3 --timeout 120

Deployment to Digital Ocean Droplet

Create a droplet in Digital Ocean. You can follow the steps mentioned here on their website with SSH key. I chose the Ubuntu 24.04 LTS version with $6/month plan for the purpose of this tutorial.

Now from your choice of terminal, login to your droplet with SSH using your droplet IP which you can find after the droplet is successfully created with SSH key:

ssh root@your-droplet-ip-address

Or you can even connect with droplet console from the Digital Ocean platform:

Image.png

Update, upgrade, and install all the required packages for this setup (if they aren't already) including Docker with the following commands (Command from Docker docs https://docs.docker.com/engine/install/ubuntu/):

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

# Command to install latest version
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Test docker installation
sudo docker run hello-world

Image.png

Now clone the Git project into the droplet with the git clone command, move into the directory, and set up environment variables.

By this point, your project directory should look like this:

django-app/
├── .env.example           # Example environment variables template
├── .gitignore             # Git ignore rules
├── .python-version        # Python version specification
├── Dockerfile             # Docker configuration
├── README.md             # Project documentation
├── docker-compose.yml    # Docker Compose configuration
├── manage.py             # Django management script
├── pyproject.toml        # Python project dependencies (using uv)
├── uv.lock               # Lock file for uv dependencies
│
├── core/                 # Main Django project settings
│   ├── __init__.py
│   ├── asgi.py          # ASGI configuration
│   ├── settings.py      # Project settings
│   ├── urls.py          # Main URL configuration
│   └── wsgi.py          # WSGI configuration
│
├── products/            # Products Django app
│   ├── __init__.py
│   ├── admin.py        # Admin interface configuration
│   ├── apps.py         # App configuration
│   ├── migrations/     # Database migrations
│   ├── models.py       # Database models
│   ├── serializers.py  # DRF serializers
│   ├── tests.py        # Test cases
│   ├── urls.py         # App URL configuration
│   └── views.py        # View logic
│
├── media/              # User-uploaded media files
├── nginx/              # Nginx configuration
├── scripts/            # Project scripts
└── staticfiles/        # Collected static files
git clone https://github.com/your-username/django-app.git
cd django-app
touch .env
vi .env
# .env

DJANGO_SECRET_KEY=
DJANGO_DEBUG=       # False for prod, True for testing locally
DJANGO_ALLOWED_HOSTS=     # localhost,127.0.0.1 for testing locally
DATABASE_URL=your-postgres-database-url

Now run the Docker command to get the Django REST API up and running.

Update the nginx.conf file for running it successfully on the droplet:

upstream django_app {
    server web:8000;
}

server {
    listen 80 default_server;
    server_name _;
    client_max_body_size 100M;

    # Better handling of proxy headers
    proxy_http_version 1.1;
    proxy_set_header Connection "upgrade";
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;

    location / {
        proxy_pass http://django_app;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_buffering off;
    }

    location /static/ {
        alias /app/staticfiles/;
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    location /media/ {
        alias /app/media/;
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }
}
docker compose up -d --build

This command will get the REST API running on your droplet IP. Go to your browser or send a curl request to this endpoint for CRUD operations (Note: it will be on HTTP). You will see the following response:

http://your-droplet-ip/api/products/

Image.png

Conclusion

Congratulations! You now have your simple CRUD Django REST API created with Django and Django REST Framework up and running with uv as the Python package manager.

I'd love to hear about your experience building this API! Did you encounter any challenges? Do you have ideas for extending this tutorial? Share your thoughts with me directly through my portfolio.

References

  1. Using uv in Docker | uv

  2. ##astral-sh / ##uv-docker-example

  3. UV with Django

  4. How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu | DigitalOcean

  5. How to Create a Droplet | DigitalOcean Documentation

  6. More about uv and its rise in popularity: https://hn.algolia.com/?dateRange=pastYear&page=0&prefix=false&query=uv&sort=byPopularity&type=story

  7. Using uv as an installer

  8. https://docs.docker.com/engine/install/ubuntu/

0
Subscribe to my newsletter

Read articles from Parshva Shah directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Parshva Shah
Parshva Shah