How to Build a Production-Ready Django Project Structure

This article explains why the default Django project layout is unsuitable for production, then presents a detailed, battle‑tested directory structure, split settings, environment variable management, organized apps, services, selectors, testing layout, Makefile shortcuts, and Django 5.2 considerations to help developers create maintainable, secure, and scalable Django applications.

DevOps Coach
DevOps Coach
DevOps Coach
How to Build a Production-Ready Django Project Structure

Why the default Django layout fails

Running django-admin startproject creates a simple layout that works for tutorials but quickly becomes problematic in production: a single settings.py with many if DEBUG checks, tangled code, hard‑coded secrets, and no place for tests or documentation.

Production‑ready directory layout

The author proposes a concrete layout that separates configuration, apps, static assets, and documentation.

myproject/
├── .env.example
├── .gitignore
├── docker-compose.yml
├── Dockerfile
├── Makefile
├── README.md
├── requirements/
│   ├── base.txt
│   ├── development.txt
│   ├── production.txt
│   └── testing.txt
├── config/
│   ├── __init__.py
│   ├── settings/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── development.py
│   │   ├── production.py
│   │   └── testing.py
│   ├── urls.py
│   ├── wsgi.py
│   └── asgi.py
├── apps/
│   ├── __init__.py
│   ├── core/
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── utils.py
│   │   └── exceptions.py
│   └── users/
│       ├── __init__.py
│       ├── admin.py
│       ├── apps.py
│       ├── models.py
│       ├── views.py
│       ├── urls.py
│       ├── serializers.py
│       ├── services.py
│       ├── selectors.py
│       └── tests/
│           ├── __init__.py
│           ├── test_models.py
│           ├── test_views.py
│           └── test_services.py
├── static/
├── media/
├── templates/
├── scripts/
├── docs/
└── manage.py

Split settings per environment

Create a config/settings package with base.py holding shared values and separate development.py, production.py, and testing.py files that import the base and override specifics. Secrets are read from environment variables using python‑decouple.

# config/settings/base.py
import os
from pathlib import Path
from decouple import config, Csv

BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = config('SECRET_KEY')
# ...rest of shared settings...

Environment switching

Set the DJANGO_SETTINGS_MODULE variable to the desired module, either in the shell or in a .env file.

# .env
DJANGO_SETTINGS_MODULE=config.settings.development
# or production, testing

Organising apps

All reusable Django apps live under apps/. A dedicated core app stores shared models, utilities, and custom exceptions. Business logic resides in services, while query helpers live in selectors, keeping views thin.

Makefile for common commands

# Makefile
.PHONY: install run migrate test lint clean docker-up docker-down
install:
    pip install -r requirements/development.txt
run:
    python manage.py runserver
migrate:
    python manage.py makemigrations
    python manage.py migrate
test:
    pytest --cov=apps --cov-report=html
lint:
    ruff check apps/
    ruff format apps/
docker-up:
    docker-compose up -d
docker-down:
    docker-compose down

Testing layout

Each app contains a tests/ package with separate files for models, views, services, and selectors. A top‑level conftest.py provides shared fixtures.

Django 5.2 considerations

Long‑term support until April 2028.

Automatic model imports in the shell.

Composite primary key support (future topic).

Requires Python 3.10 or newer.

Quick bootstrap script

#!/bin/bash
mkdir -p myproject/{config/settings,apps/core,apps/users/tests,requirements,static,media,templates,scripts,docs}
cd myproject
git init
python -m venv .venv
source .venv/bin/activate
pip install django python-decouple
django-admin startproject config .
mkdir -p config/settings
mv config/settings.py config/settings/base.py
touch config/settings/__init__.py
touch config/settings/{development,production,testing}.py
touch apps/__init__.py apps/core/__init__.py apps/users/__init__.py apps/users/tests/__init__.py
touch requirements/{base,development,production,testing}.txt
touch .env .env.example .gitignore Makefile README.md
echo 'Project structure created!'

Key takeaways

Split settings per environment to avoid tangled if DEBUG blocks.

Store secrets in environment variables, never in source control.

Group all apps under a single apps/ directory for clean imports.

Use a core app for shared utilities and base models.

Move business logic to services and selectors, keeping views lightweight.

Separate dependency files for each environment.

Provide a Makefile to run common tasks with a single command.

backendPythonDevOpsDjangoproject-structure
DevOps Coach
Written by

DevOps Coach

Master DevOps precisely and progressively.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.