Report this

What is the reason for this report?

How to Deploy a Django + Next.js Application on DigitalOcean App Platform

Posted on March 14, 2026
KFSys

By KFSys

System Administrator

Introduction

Modern web applications often separate the backend API from the frontend interface. Django provides a robust backend with Django REST Framework for building APIs, while Next.js offers server-side rendering, static generation, and an optimized developer experience for the frontend.

In this tutorial, you will build a small event listing application with a Django REST API backend and a Next.js frontend, then deploy both components to DigitalOcean’s App Platform — a fully managed Platform-as-a-Service (PaaS) that handles infrastructure, SSL, and scaling for you.

By the end of this tutorial, you will have:

  • A Django REST API serving event data from a PostgreSQL database
  • A Next.js frontend consuming the API with server-side rendering
  • Both applications deployed and running on DigitalOcean App Platform
  • A CI/CD pipeline that auto-deploys on every push to your repository

Prerequisites

Before you begin, you will need:

  • A DigitalOcean account (If you’re new, you get $200 in free credits for 60 days)
  • A GitHub account with your code pushed to a repository
  • Python 3.10+ installed locally
  • Node.js 18+ and npm installed locally
  • Git installed locally
  • Basic familiarity with Django and Next.js


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

These answers are provided by our Community. If you find them useful, show some love by clicking the heart. If you run into issues leave a comment, or add your own answer to help others.
0

Accepted Answer

Step 1 — Setting Up the Django Backend

First, create a project directory and set up the Django backend.

Create the project structure:

mkdir django-nextjs-app && cd django-nextjs-app
mkdir backend && cd backend

Create and activate a Python virtual environment:

python3 -m venv venv
source venv/bin/activate

Install Django and the required packages:

pip install django djangorestframework django-cors-headers gunicorn psycopg2-binary dj-database-url python-decouple whitenoise

Create a new Django project and app:

django-admin startproject config .
python manage.py startapp events

Your backend/ directory should now look like this:

backend/
├── config/
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── events/
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── views.py
│   └── ...
├── manage.py
└── venv/

Step 2 — Configuring Django for Production

Open backend/config/settings.py and update it to support environment variables and production deployment:

# backend/config/settings.py

import os
from pathlib import Path
from decouple import config, Csv
import dj_database_url

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = config('DJANGO_SECRET_KEY', default='change-me-in-production')

DEBUG = config('DJANGO_DEBUG', default=False, cast=bool)

ALLOWED_HOSTS = config('DJANGO_ALLOWED_HOSTS', default='localhost,127.0.0.1', cast=Csv())

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Third party
    'rest_framework',
    'corsheaders',
    # Local
    'events',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'config.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'config.wsgi.application'

# Database
DATABASES = {
    'default': dj_database_url.config(
        default=config('DATABASE_URL', default=f'sqlite:///{BASE_DIR / "db.sqlite3"}'),
        conn_max_age=600,
    )
}

# Static files
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STORAGES = {
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
    },
}

# CORS
CORS_ALLOWED_ORIGINS = config(
    'CORS_ALLOWED_ORIGINS',
    default='http://localhost:3000',
    cast=Csv()
)

# REST Framework
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
}

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

This configuration uses python-decouple to read environment variables, dj-database-url to parse the database connection string, and whitenoise to serve static files efficiently in production.

Step 3 — Creating the Events API

Define the Event model in backend/events/models.py:

# backend/events/models.py

from django.db import models


class Event(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField()
    date = models.DateTimeField()
    location = models.CharField(max_length=200)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['date']

    def __str__(self):
        return self.title

Create a serializer in backend/events/serializers.py:

# backend/events/serializers.py

from rest_framework import serializers
from .models import Event


class EventSerializer(serializers.ModelSerializer):
    class Meta:
        model = Event
        fields = ['id', 'title', 'description', 'date', 'location', 'created_at']

Create the API view in backend/events/views.py:

# backend/events/views.py

from rest_framework import generics
from .models import Event
from .serializers import EventSerializer


class EventListView(generics.ListAPIView):
    queryset = Event.objects.all()
    serializer_class = EventSerializer

Wire up the URLs in backend/events/urls.py:

# backend/events/urls.py

from django.urls import path
from .views import EventListView

urlpatterns = [
    path('events/', EventListView.as_view(), name='event-list'),
]

Include the events URLs in backend/config/urls.py:

# backend/config/urls.py

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

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

Run the migrations and create some sample data:

python manage.py makemigrations events
python manage.py migrate
python manage.py createsuperuser

Start the development server and add a few events through the admin at http://localhost:8000/admin/:

python manage.py runserver

Verify the API works by visiting http://localhost:8000/api/events/ in your browser.

Step 4 — Preparing the Django Backend for Deployment

Create a backend/requirements.txt file:

pip freeze > requirements.txt

Create a backend/runtime.txt to specify the Python version:

python-3.11.7

Create a backend/.env.example for reference (do not commit your actual .env file):

DJANGO_SECRET_KEY=your-secret-key
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=.ondigitalocean.app
DATABASE_URL=postgresql://user:password@host:port/dbname
CORS_ALLOWED_ORIGINS=https://your-frontend.ondigitalocean.app

Step 5 — Building the Next.js Frontend

Navigate back to the root directory and create the Next.js app:

cd ..
npx create-next-app@latest frontend --typescript --tailwind --app --eslint --src-dir --no-import-alias
cd frontend

Create an API utility to fetch data from the Django backend. Create frontend/src/lib/api.ts:

// frontend/src/lib/api.ts

const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';

export interface Event {
  id: number;
  title: string;
  description: string;
  date: string;
  location: string;
  created_at: string;
}

export async function getEvents(): Promise<Event[]> {
  const res = await fetch(`${API_BASE_URL}/api/events/`, {
    next: { revalidate: 60 },
  });

  if (!res.ok) {
    throw new Error('Failed to fetch events');
  }

  return res.json();
}

Replace the contents of frontend/src/app/page.tsx with the events listing page:

// frontend/src/app/page.tsx

import { getEvents } from '@/lib/api';

export default async function Home() {
  const events = await getEvents();

  return (
    <main className="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
      <div className="max-w-4xl mx-auto">
        <h1 className="text-4xl font-bold text-gray-900 mb-2">
          Upcoming Events
        </h1>
        <p className="text-gray-600 mb-8">
          Browse the latest events happening near you.
        </p>

        {events.length === 0 ? (
          <p className="text-gray-500 text-center py-12">
            No events found. Check back later!
          </p>
        ) : (
          <div className="grid gap-6">
            {events.map((event) => (
              <article
                key={event.id}
                className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow"
              >
                <div className="flex justify-between items-start">
                  <div>
                    <h2 className="text-xl font-semibold text-gray-900">
                      {event.title}
                    </h2>
                    <p className="text-gray-600 mt-2">{event.description}</p>
                  </div>
                </div>
                <div className="mt-4 flex flex-wrap gap-4 text-sm text-gray-500">
                  <span className="flex items-center gap-1">
                    📅{' '}
                    {new Date(event.date).toLocaleDateString('en-US', {
                      weekday: 'long',
                      year: 'numeric',
                      month: 'long',
                      day: 'numeric',
                    })}
                  </span>
                  <span className="flex items-center gap-1">
                    📍 {event.location}
                  </span>
                </div>
              </article>
            ))}
          </div>
        )}
      </div>
    </main>
  );
}

Update frontend/src/app/layout.tsx:

// frontend/src/app/layout.tsx

import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: 'Event Listings — Django + Next.js on DigitalOcean',
  description: 'A demo event listing app built with Django and Next.js, deployed on DigitalOcean App Platform.',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  );
}

Test the frontend locally (make sure your Django server is running on port 8000):

npm run dev

Visit http://localhost:3000 to confirm events render correctly.

Step 6 — Preparing the Next.js Frontend for Deployment

DigitalOcean App Platform needs Next.js to run in standalone mode to minimize the deployed bundle size. Update frontend/next.config.ts:

// frontend/next.config.ts

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  output: 'standalone',
};

export default nextConfig;

Step 7 — Setting Up the GitHub Repository

Initialize a Git repository from the project root:

cd ..  # back to django-nextjs-app/
git init

Create a .gitignore in the root:

# Python
backend/venv/
backend/__pycache__/
backend/**/__pycache__/
backend/db.sqlite3
backend/.env
*.pyc

# Node
frontend/node_modules/
frontend/.next/
frontend/.env.local

# OS
.DS_Store

Commit and push:

git add .
git commit -m "Initial commit: Django + Next.js event app"
git remote add origin https://github.com/your-username/django-nextjs-app.git
git branch -M main
git push -u origin main

Replace your-username with your actual GitHub username.

Step 8 — Creating a Managed PostgreSQL Database on DigitalOcean

Before deploying the app, set up a managed database:

  1. Log in to your DigitalOcean Cloud Panel
  2. Click Databases in the left sidebar
  3. Click Create Database Cluster
  4. Choose PostgreSQL (version 16)
  5. Select a datacenter region close to your users
  6. Choose the Basic Plan ($15/month — suitable for development and small production workloads)
  7. Name it events-db
  8. Click Create Database Cluster

Once provisioning completes (typically 3–5 minutes):

  1. Click into your database cluster
  2. Under Connection Details, note the values for host, port, username, password, and database
  3. Copy the full Connection String — you’ll need it shortly

Step 9 — Deploying to DigitalOcean App Platform

Now deploy both the Django backend and Next.js frontend as components of a single App Platform application.

Create the App

  1. Navigate to App Platform in the DigitalOcean dashboard
  2. Click Create App
  3. Select GitHub as the source and authorize DigitalOcean to access your repository
  4. Select the django-nextjs-app repository and the main branch

Configure the Backend Component

  1. Click Edit on the detected component, or Add ResourceService

  2. Set the following:

    • Name: backend
    • Source Directory: /backend
    • Build Command: pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate
    • Run Command: gunicorn config.wsgi --bind 0.0.0.0:8080
    • HTTP Port: 8080
    • Instance Size: Basic ($5/month)
  3. Under Environment Variables, add:

Variable Value
DJANGO_SECRET_KEY (click “Generate” or paste a strong random string)
DJANGO_DEBUG False
DJANGO_ALLOWED_HOSTS .ondigitalocean.app
DATABASE_URL (paste the connection string from Step 8)
CORS_ALLOWED_ORIGINS (leave blank for now — you’ll update this after the frontend deploys)

Tip: Mark DJANGO_SECRET_KEY and DATABASE_URL as Encrypted to keep them secure.

Configure the Frontend Component

  1. Click Add ResourceService

  2. Set the following:

    • Name: frontend
    • Source Directory: /frontend
    • Build Command: npm ci && npm run build
    • Run Command: node .next/standalone/server.js
    • HTTP Port: 3000
    • Instance Size: Basic ($5/month)
  3. Under Environment Variables, add:

Variable Value
NEXT_PUBLIC_API_URL https://backend-xxxxx.ondigitalocean.app (use the backend’s URL from App Platform)
PORT 3000

Attach the Database

  1. Click Add ResourceDatabase
  2. Select Previously Created Database and choose the events-db cluster from Step 8
  3. App Platform will automatically inject the DATABASE_URL environment variable

Deploy

Click Create Resources. DigitalOcean will now:

  • Pull your code from GitHub
  • Build both components
  • Run database migrations
  • Start serving your application

The first deployment takes approximately 5–10 minutes.

Step 10 — Updating CORS After Deployment

Once both components are live, you need to update the backend’s CORS configuration with the frontend’s actual URL.

  1. Go to your app in the App Platform dashboard
  2. Click on the backend component → SettingsEnvironment Variables
  3. Update CORS_ALLOWED_ORIGINS to your frontend URL, for example:
    https://frontend-xxxxx.ondigitalocean.app
    
  4. Click Save — this triggers a redeployment of the backend

After the redeployment finishes, visit your frontend URL. You should see your events listed, rendered server-side by Next.js and served from your Django API.

Step 11 — Setting Up Automatic Deployments

DigitalOcean App Platform supports automatic deployments out of the box. Every time you push to the main branch, both components rebuild and redeploy automatically.

To verify this:

# Make a small change, then push
git add .
git commit -m "Update event listing styles"
git push origin main

Within minutes, your live application reflects the changes — no manual intervention required.

Step 12 — Adding a Custom Domain (Optional)

To serve your app on a custom domain:

  1. In App Platform, go to SettingsDomains
  2. Click Add Domain
  3. Enter your domain name
  4. Add the provided CNAME record in your domain’s DNS settings
  5. DigitalOcean automatically provisions a free Let’s Encrypt SSL certificate

Remember to update DJANGO_ALLOWED_HOSTS and CORS_ALLOWED_ORIGINS with your custom domain as well.

This approach of pairing Django with Next.js on a managed platform is well-suited for content-driven and event-driven applications — it’s a similar architecture behind production sites like extratime.world, which serves sports event data to users worldwide.

Conclusion

In this tutorial, you built a full-stack application with a Django REST Framework backend and a Next.js frontend, then deployed both to DigitalOcean App Platform with a managed PostgreSQL database.

Your deployed application now has:

  • Server-side rendering via Next.js for fast initial page loads and SEO
  • Automatic HTTPS via DigitalOcean’s managed SSL
  • CI/CD with automatic deployments on every Git push
  • Managed database with automated backups and failover
  • Independent scaling — backend and frontend can be scaled separately

From here, you can extend the application by adding user authentication with Django’s auth system and Next.js middleware, implementing real-time updates with WebSockets via Django Channels, or adding an admin interface to manage events through the Django admin panel.

Great write up, thanks for sharing this!

Quick question: if you wanted to add Celery for background tasks (for example sending emails, processing uploads, or scheduled jobs), how would you integrate that into this setup on App Platform? Would you run Celery as a separate worker service in the same app or handle it differently?

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.