Django Base Site
The Django Base Site is an opinionated starter template to jump-start any new Django project. It has been lovingly maintained for over twelve years and has been used to jump-start many other projects. Because it is organized logically, it will help you and your team collaborate and stay organized as your project grows. All the best practices and tools are used to help you save days of mundane setup and tooling! This Django boilerplate/starter template will help you or your team deploy your site to production in minutes instead of days.
To get started, jump to the installation section or keep reading to learn more about the included features.
✨ Features
🧑💻 Best Practices
- Epicenv - A delightful environment variable manager with schema validation, type coercion, and CLI tools for generating
.envfiles. See the epicenv documentation for more details. - Docker - Docker Compose for development with healthchecks on every service, plus a multi-stage Dockerfile for a production-ready image.
- Mailpit - Local SMTP capture with a web UI at http://localhost:8025
- MinIO - S3-compatible object storage for local media uploads (avatars, etc.) with a console at http://localhost:9001
- UV - Used to maintain Python requirements
- Just - Popular tool for running common commands (make equivalent)
- python-json-logger and readable-log-formatter - JSON logging for better log parsing
📦️ Django Packages
- Django 5 - Latest version of Django
- Custom User Model - Extends
AbstractUserwith per-usertimezone(auto-detected from the browser via middleware) and avatar fields (uploaded to MinIO/S3 with crop data). - Django Allauth (headless) - JSON auth API with full MFA support: TOTP, recovery codes, and WebAuthn passkeys (via
fido2). - Django Ninja - Fast type-safe API framework powering
/api/app-context/, the user/avatar endpoints, organizations, teams, and the public invite flow. - Django Hijack - Staff impersonation with a SPA-driven user search, gated by a
staff_onlypermission check. - Celery - Most popular task runner for running asynchronous tasks in the background.
- Gunicorn - Production WSGI server (4 workers × 2 threads), configured at
gunicorn.conf.py. - WhiteNoise - Serves Vite-hashed assets in production with
Cache-Control: max-age=31536000, immutable. - Django Storages + boto3 - S3-compatible media storage with a custom backend (
apps.base.storage.S3MediaStorage) that handles the Docker-internal vs. browser endpoint URL split for MinIO. - Django Alive - Health-check endpoints
- Django Maintenance Mode - Drop the site into a maintenance window.
- Django SES - Production email backend.
🧩 Custom Django Applications
These first-party apps under apps/ are the foundation for any B2B SaaS built on this template.
apps/organizations/— multi-tenant scaffolding:Organization,OrganizationMember(withis_owner/is_primaryflags), andOrganizationInvitemodels.OrganizationMiddlewarelazy-loadsrequest.orgfrom the session; an org switcher lives in the user-menu navbar. The accept-invite flow lives in the SPA at/organizations/invite/:key/accept/and works for anonymous visitors via a sign-in / sign-up roundtrip with?next=.apps/teams/— genericTeammodel (organization FK + M2M users) with full CRUD ninja API.apps/notifications/—Notificationmodel (recipient + org + GenericForeignKey target) andNotificationPreferencemodel, ninja API at/api/notifications/(list, unread-count, bulk, per-row patch/delete, per-user category preferences), anotify()producer service, retention via thepurge_expired_notificationscelery beat task and thepurge_notificationsmanagement command, and a bell-dropdown UI with 20-second polling that pauses on hidden tabs. Producers register their models insettings.NOTIFICATIONS_TARGET_MODELSso target deletes cascade-clean their notifications, and declare categories insettings.NOTIFICATIONS_CATEGORIESso users can opt channels in or out per category.
🔧 Python Testing Tools
- Pytest - The most popular Python test runner in the Python community
- Pytest Django - Django plugin for Pytest
- Pytest-cov - Code coverage
- Pytest Playwright - End-to-end tests live under
e2e/and run against a dedicatedconfig.settings.e2esettings module with pre-built Vite assets. - Model Bakery - Faster fixture creation
- Django Test Plus - Helper functions to write tests faster
- pyotp - Used in the MFA / TOTP test suite
🐛 Debugging Support
- Remote Debugging - Full debugging support for PyCharm, VS Code, LazyVim/Neovim, and any DAP-compatible editor
- One Command Setup - Start debugging with
just start_with_debugpy - PyCharm Integration - Native Docker Compose debugging support with hot-reload
- VS Code Integration - Pre-configured launch configurations and tasks
- Detailed Documentation - Complete setup guides for all supported editors
🩺 Code Quality, Formatting, and Linting Tools
- Ruff - Python formatting and linting (replaces Black and friends, written in Rust).
- Ty - Python type checking
- dj Lint - Automatic Django HTML template formatting and linting
- Django Debug Toolbar - Inspect query counts, settings, and templates in DEBUG.
- Eslint (flat config) with
eslint-plugin-vue- JS/Vue linting
💄 Frontend
- Vue 3 SPA - Lazy-loaded routes via Vue Router 5, reactive app store, theme-applied-before-CSS to prevent flash, version watcher with deploy-update banner.
- Tailwind CSS v4 - Utility-first styling with the @tailwindcss/vite plugin and a custom
darkvariant driven bydata-theme. - Vite 8 - Frontend build tool. Hashed-asset cache + manifest support so WhiteNoise can serve them with immutable headers.
- bun - Fast JS toolchain (replaces npm). The
frontendDocker service is built fromoven/bun:1. - @heroicons/vue, vue-advanced-cropper, reka-ui - UI primitives.
- Account settings (General, Email, Password change, Security with TOTP / recovery codes / passkeys, Notifications), org settings (General, Members + Invites, Teams, Billing), notification bell with polling and a
/notifications/archive page, public/pricing/page, Test Notifications and Impersonate views (superuser/staff).
💳 Billing (opt-in)
Stripe-backed subscriptions tied to Organizations. Off by default — set BILLING_ENABLED=true to turn on.
apps/billing/withBillingCustomer,Subscription, andWebhookEventmodels. Stripe is the source of truth; we mirror just enough locally to gate features and render the billing UI.- Settings-declared plan + feature registries (
BILLING_PLANS,BILLING_FEATURES) — same pattern asNOTIFICATIONS_CATEGORIES. Plans support monthly/annual prices, free tier, trial periods, per-seat pricing, and a "highlighted" flag for the popular plan. - Stripe Checkout (full-page redirect) for new subscriptions and Stripe Customer Portal for upgrades, cancels, payment-method updates, and invoice history. Coupons / promotion codes are accepted at Checkout.
- Webhook at
/webhooks/stripe/with HMAC signature verification andWebhookEvent-backed dedupe of Stripe retries. - Per-seat sync — adding/removing org members updates Stripe quantity via
transaction.on_commitin aOrganizationMemberpost_save/post_deletereceiver. - Trial-ending reminders + drift-recovery reconcile run as celery beat tasks.
- Feature gating:
appStore.hasFeature('teams')in the SPA,org_has_feature(org, 'teams')and@requires_feature('teams')in Python. WhenBILLING_ENABLED=False, every feature falls through to its declared default so the starter template runs without Stripe credentials. - Local dev:
stripe listen --forward-to http://localhost:8000/webhooks/stripe/to bridge the test-mode webhooks.
📝 Documentation
The Django Base Site uses Zensical (the modern successor to MkDocs + Material for MkDocs, built by the same team) for its documentation.
Documentation source lives in the docs/ directory. Configuration is in zensical.toml at the project root. You can copy the docs when starting a new project if you want to maintain documentation in the same style.
Installation
Requirements
Before proceeding make sure you have installed Docker and Just. Docker with Docker Compose is used for local development and Just is used for common project commands.
Quickstart Install Script
Copy and paste the following into your terminal to run the install script:
bash <(curl -s https://raw.githubusercontent.com/epicserve/django-base-site/main/scripts/start_new_project)
Running the script mostly does the same thing as manual method. The exception is that the install script has questions to customize your new project setup.
Note: When starting the Django runserver it will take several seconds before the CSS styles take effect. This is because Vite is running in dev mode which takes a few seconds to take effect.
Example output:
$ cd ~/Sites
$ bash <(curl -s https://raw.githubusercontent.com/epicserve/django-base-site/main/scripts/start_new_project)
What is the project name slug [example]?
What directory do you want your project in [/Users/brento/Sites/example]?
Done.
To start Docker Compose run:
$ cd /Users/brento/Sites/example
$ just start
Manual Installation
$ curl -LOk https://github.com/epicserve/django-base-site/archive/main.zip && unzip main
$ mv django-base-site-main example
$ cd example
$ uvx epicenv create # Generates .env from the schema in .env.toml
$ just clean_extra_files
$ find ./public -name ".keep" | xargs rm -rf
$ just start
epicenv create reads the [variables] block in .env.toml and produces a .env with sensible defaults — SECRET_KEY and POSTGRES_PASSWORD are auto-generated, SITE_DOMAIN=localhost:8000 (so passkeys work), and the MinIO + Mailpit credentials are pre-wired.
Usage
Dev URLs
Once just start is up, the following are available:
| URL | What |
|---|---|
| http://localhost:8000/ | Vue SPA (Django serves the shell, SPA owns routing) |
| http://localhost:8000/admin/ | Django admin |
| http://localhost:8000/api/docs | Live OpenAPI spec for the ninja API (DEBUG only) |
| http://localhost:3000/ | Vite dev server (HMR; usually proxied transparently) |
| http://localhost:8025/ | Mailpit — inspect outgoing emails |
| http://localhost:9001/ | MinIO console — browse the media bucket |
Note: Use
http://localhost:8000/rather thanhttp://127.0.0.1:8000/so passkey enrollment works — WebAuthn rejects bare IP addresses as Relying Party IDs.
Django superuser
On a fresh project run just init — it brings services up, runs just create_superuser (which calls the idempotent epicenv create-superuser), then attaches to the foreground logs like a normal just start. Use just start for every subsequent boot so you don't pay the setup overhead each time.
Two ways to provide credentials:
- Env-var path (simplest). Set
DJANGO_SUPERUSER_USERNAME,DJANGO_SUPERUSER_EMAIL, andDJANGO_SUPERUSER_PASSWORDin.env(the variables are declared in.env.tomlwith empty defaults).just initandjust create_superuserboth pick them up automatically through the container env. IfDJANGO_SUPERUSER_USERNAMEis blank, the recipe skips with a friendly message — sojust initis safe to run before you've decided on a superuser strategy. - Secrets-manager path. The
create_superuserrecipe lives in your project's top-leveljustfileso you can edit it in place. Replace the default body with a pipeline that fetches credentials from your secrets manager and pipes JSON intoepicenv create-superuser. Example for 1Password:
create_superuser:
#!/usr/bin/env bash
docker compose up --wait -d web
uvx epicenv secrets get op://Private/django-admin \
--fields username,email,password \
| docker compose exec -T web epicenv create-superuser
Since epicenv create-superuser honors stdin first and env vars last, piped credentials take priority over anything in .env. just create_superuser brings the web container up if it isn't already, so it's runnable on demand any time (e.g., after rotating the admin password).
Just commands
The Django Base Site comes with Just recipes for all the most common commands and tasks. To see the full list run just in the root of the project. Common ones:
init # First-time setup: services up + create_superuser + attach (use just start subsequently)
start # docker compose up
start_with_debugpy # Start with debugpy listening on :5678
stop # Stop all services
build # Rebuild Docker images + collectstatic
build_frontend # bun run build + collectstatic
clean # Remove build files, caches, coverage data
collectstatic # Run Django's collectstatic
format # Format Python (ruff), JS (eslint), HTML (djlint), justfile
lint # Run all linters + ty type check + check for missing migrations
test # pytest (Django + ninja API tests)
test_with_coverage # pytest --cov, then open the HTML report
test_e2e [args] # Build the frontend, then run the Playwright e2e suite
db_dump # pg_dump to ~/Downloads
db_restore [dump_file] # Restore from the latest dump (or a specific file)
upgrade_python_packages # uv sync --all-packages --all-extras
upgrade_node_packages # bun update
create_env # Generate .env from the schema in .env.toml
create_superuser # Idempotent epicenv create-superuser (override to pipe from a secrets manager)
Deploying to Production
The Django base site is designed to be production ready because it comes with a production ready multi-stage Dockerfile. You can also read a blog post about using it with fly.io. If you want to blog about using the Django Base Site with other PaaS providers, please let me know, and I can link to the post here.
Contribute
- Look for an open issue or create new issue to get a dialog going about the new feature or bug that you've discovered.
- Fork the repository on GitHub to start making your changes to the main branch (or branch off of it).
- Make a pull request.
Working on the Documentation
These docs are built with Zensical.
Use the following Just commands:
just docs # Serve locally at http://localhost:4000
just docs-build # Build the static site into docs_site/
just docs-lint # Check for broken links
You can also run the underlying commands directly:
uv run zensical serve -f zensical.toml
uv run zensical build -f zensical.toml --clean
Output is written to the docs_site/ directory.