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

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:
You can test uv
by running the hello.py file with the following command:
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:
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
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/
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
Subscribe to my newsletter
Read articles from Parshva Shah directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
