django-binary-builder
Build Django projects as installable desktop applications.
django-binary-builder is a reusable Django build library. Install it
into an existing Django project, add it to INSTALLED_APPS, and use
the binary management command to package the project into a
standalone, installable desktop application for Windows.
By default the packaged application opens as a native desktop window
(via pywebview) rather than a
browser tab — Django and Waitress still run in the background on
127.0.0.1, but the user only ever sees an app window. Closing that
window shuts the server down and exits the process. Set
SERVER.MODE = "browser" to open the default system browser instead.
Read Django settings and .env
→ Validate the build environment
→ Collect static files
→ Generate a runtime configuration snapshot
→ Generate launcher.py
→ Generate a PyInstaller spec
→ Build an onedir bundle with PyInstaller
→ Generate an Inno Setup script
→ Build a Setup.exe with Inno Setup
Requirements
- Python 3.14
- Django 6.x
- Windows 10 or Windows 11 (Windows installers must be built on a
Windows host;
linuxandmacostargets are reserved for future versions) - PyInstaller 6.x (
onedirmode only) - Inno Setup 7 (only needed when building an installer)
Installation
uv add django-binary-builder
Add the app to your Django settings:
INSTALLED_APPS = [
# Existing apps
"django_binary_builder",
]
Build a Windows installer:
uv run python manage.py binary windows
The final output is written to:
release/windows/<executable-name>-<version>-Setup.exe
CLI
python manage.py binary PLATFORM [OPTIONS]
PLATFORM accepts windows, linux, or macos (only windows is
implemented).
| Option | Behaviour |
|---|---|
--check |
Run preflight checks only; create no files. |
--generate-only |
Generate launcher, spec, runtime metadata and (optionally) .iss without running packaging tools. |
--skip-installer |
Run PyInstaller but do not require Inno Setup. |
--clean |
Delete existing build files before building. |
--name VALUE |
Override the application display name. |
--app-version VALUE |
Override the packaged application version. |
--output PATH |
Override the output directory. |
--console |
Show the console window when the application runs. |
--no-collectstatic |
Skip collecting static files. |
--env-file PATH |
Load an extra .env file after the configured files. |
--no-env |
Disable .env loading and the environment snapshot. |
Full settings example
All keys are optional; the block below shows the available options with their default values:
from pathlib import Path
DJANGO_BINARY_BUILDER = {
"NAME": "My Application",
"VERSION": "1.0.0",
"PUBLISHER": "Example Company",
"EXECUTABLE_NAME": "my-application",
"ICON": Path("assets/application.ico"), # optional .ico file
"OUTPUT_DIR": Path("release"),
"WORK_DIR": Path(".django-binary-builder"),
"SERVER": {
"HOST": "127.0.0.1", # runtime binds loopback only
"PORT": 8765,
"THREADS": 8,
"MODE": "webview", # "webview" (native window) or "browser"
"OPEN_BROWSER": True, # only used when MODE = "browser"
},
"WEBVIEW": {
"TITLE": None, # defaults to NAME
"WIDTH": 1200,
"HEIGHT": 800,
"RESIZABLE": True,
},
"ENVIRONMENT": {
"ENABLED": True,
"FILES": [Path(".env")],
"OVERRIDE_PROCESS_ENV": False, # process env wins by default
"INCLUDE": ["DJANGO_SECRET_KEY", "DJANGO_ALLOWED_HOSTS", "APP_FEATURE_*"],
"EXCLUDE": ["DJANGO_BINARY_ADMIN_PASSWORD", "DJANGO_BINARY_DB_PASSWORD"],
"REQUIRED": [],
"PACKAGE_MODE": "snapshot",
"SNAPSHOT_FILENAME": "runtime-environment.json",
"ALLOW_SECRETS": False, # opt-in for sensitive names
"WARN_ON_SECRET_NAMES": True,
},
"DATABASE": {
"MODE": "sqlite", # or "external"
"RUN_MIGRATIONS": True,
"MIGRATION_TIMEOUT": 300,
"SQLITE": {
"FILENAME": "db.sqlite3", # plain filename only
"COPY_INITIAL_DATABASE": False,
"INITIAL_DATABASE": None, # seed database to copy on first run
},
"EXTERNAL": {
"USE_PROJECT_SETTINGS": True,
"CONFIG_FILE": "database.json",
"ALLOW_ENVIRONMENT_VARIABLES": True,
"TEST_CONNECTION_ON_STARTUP": True,
},
},
"INITIAL_ADMIN": {
"ENABLED": True,
"SQLITE_ONLY": True,
"USERNAME": "admin",
"PASSWORD": "admin1234",
"EMAIL": "admin@localhost",
"EXTRA_FIELDS": {},
"REQUIRE_PASSWORD_CHANGE": True,
"RESET_PASSWORD_IF_USER_EXISTS": False, # not implemented; warns
},
"RUNTIME": {
"COMPANY_DIRECTORY": "ExampleCompany",
"APPLICATION_DIRECTORY": "MyApplication",
"DATA_DIRECTORY": None, # override for %LOCALAPPDATA% default
"LOG_DIRECTORY": "logs",
"MEDIA_DIRECTORY": "media",
"CONFIG_DIRECTORY": "config",
},
"BUILD": {
"MODE": "onedir", # onefile is not supported
"CONSOLE": False,
"CLEAN": True,
"COLLECT_STATIC": True,
"HIDDEN_IMPORTS": [],
"EXCLUDED_MODULES": [],
"EXTRA_DATA": [], # [{"source": ..., "destination": ...}]
},
"WINDOWS": {
"INNO_SETUP_COMPILER": None, # explicit ISCC.exe path
"PRIVILEGES": "lowest",
"ARCHITECTURE": "x64compatible",
"CREATE_DESKTOP_SHORTCUT": True,
"CREATE_START_MENU_SHORTCUT": True,
},
}
Windows build prerequisites
- Build on a Windows 10 or Windows 11 host (no cross-compilation).
- Install Inno Setup 7 — the
default install locations and
PATHare searched automatically, or setWINDOWS.INNO_SETUP_COMPILERto the fullISCC.exepath. Use--skip-installerwhile iterating to skip this requirement. - Configure
STATIC_ROOTin your Django settings whenBUILD.COLLECT_STATICis enabled.
.env snapshot handling
At build time the library reads your .env files (configured list,
then --env-file, then <project root>/.env by default), merges them
with the process environment (process environment wins unless
OVERRIDE_PROCESS_ENV=True), applies INCLUDE/EXCLUDE glob
patterns (EXCLUDE always wins; an empty INCLUDE selects nothing;
use * to include everything), and writes the selected variables to a
runtime-environment.json snapshot bundled with the application.
Security warning: a PyInstaller bundle or Windows installer cannot
safely hide embedded secrets. Any password, API key or Django secret
packaged into a client-side application can be extracted by anyone
with file access. ALLOW_SECRETS=True means you explicitly accept
this risk — it does not mean the values are encrypted or
protected. Variable names matching *SECRET*, *PASSWORD*,
*TOKEN*, *API_KEY*, *PRIVATE_KEY*, *DATABASE_URL* or
*DB_PASSWORD* are treated as sensitive: the build fails unless
ALLOW_SECRETS=True, and the build log only ever shows variable names
with [REDACTED] values.
At runtime the process environment always wins over the snapshot, so
IT administrators can override any bundled value (including the
DJANGO_BINARY_DB_* and DJANGO_BINARY_ADMIN_* variables below).
External database
DATABASE.MODE = "external" keeps your project's DATABASES
settings (USE_PROJECT_SETTINGS=True) or reads a
config/database.json file in the runtime data directory:
{
"engine": "django.db.backends.postgresql",
"name": "mydb",
"user": "myuser",
"password": "changeme",
"host": "localhost",
"port": 5432
}
Precedence: runtime process environment → database.json → project
DATABASES. The supported environment variables are
DJANGO_BINARY_DB_ENGINE, DJANGO_BINARY_DB_NAME,
DJANGO_BINARY_DB_USER, DJANGO_BINARY_DB_PASSWORD,
DJANGO_BINARY_DB_HOST and DJANGO_BINARY_DB_PORT.
Install the driver yourself — the builder does not install database servers or drivers:
uv add "psycopg[binary]" # PostgreSQL
uv add mysqlclient # MySQL / MariaDB
uv add oracledb # Oracle
Preflight verifies the driver for your engine and adds the matching
hidden imports to the PyInstaller build. The database password is
never written to the snapshot by default — provide it through the
environment or database.json on the deployment machine instead.
SQLite runtime location
With DATABASE.MODE = "sqlite" the packaged application stores its
database in a persistent, per-user runtime directory:
%LOCALAPPDATA%/<CompanyDirectory>/<ApplicationDirectory>/
├── data/db.sqlite3
├── config/database.json
├── media/
├── logs/application.log
└── state/initialization.json
Override the root with the DJANGO_BINARY_DATA_DIR environment
variable or RUNTIME.DATA_DIRECTORY. The installer never deletes
this directory, and an existing user database is never overwritten
during upgrades or reinstalls.
Initial administrator (security warning)
On first startup in SQLite mode the packaged application creates an
initial administrator using publicly documented default
credentials: username admin, password admin1234. Build and
runtime logs display warnings about this credential without ever
printing the password. Change the password immediately after the
first login.
Override the credentials at runtime with
DJANGO_BINARY_ADMIN_USERNAME, DJANGO_BINARY_ADMIN_PASSWORD and
DJANGO_BINARY_ADMIN_EMAIL (process environment wins). On later
startups the account is left completely untouched — the password is
never reset, the email is never changed, and existing users are never
promoted. INITIAL_ADMIN.RESET_PASSWORD_IF_USER_EXISTS=True is not
implemented and only produces a warning.
REQUIRE_PASSWORD_CHANGE=True records a reminder in the state file
and keeps logging a warning; it does not technically force a
password change unless you add your own middleware or login flow.
Build, install and runtime data separation
- Build time happens on your development machine: static files
are collected, artifacts are generated under
.django-binary-builder/windows/, and packaging tools run there. - Install time copies the onedir bundle to
{localappdata}\Programs\<ApplicationName>with per-user privileges. - Runtime data (SQLite database, media, logs, state) lives in
%LOCALAPPDATA%and survives upgrades and uninstalls.
Troubleshooting
--versionconflict / CLI errors — the command deliberately does not register--version; use--app-version.STATIC_ROOT must be configured— setSTATIC_ROOTor pass--no-collectstatic.Inno Setup 7 was not found— install Inno Setup 7, addISCC.exetoPATH, setWINDOWS.INNO_SETUP_COMPILER, or build with--skip-installer.Sensitive environment variables were selected ...— remove the variable fromENVIRONMENT.INCLUDE, or setENVIRONMENT.ALLOW_SECRETS = Trueto accept the risk.- The packaged app cannot connect to an external database — check
DJANGO_BINARY_DB_*environment variables andconfig/database.json; the connection error messages are redacted. - Port 8765 is busy — the launcher automatically picks the next available port and opens the browser at the actual URL.
- Migrations are slow on startup — migrations run on every
startup;
DATABASE.MIGRATION_TIMEOUTonly controls a warning threshold, it does not abort.
Known limitations
- Windows only (
linux/macostargets are reserved but not implemented); no cross-compilation. - PyInstaller
onedironly; noonefile. - WSGI only — no ASGI, Django Channels or WebSocket support.
- No Celery worker/beat, no bundled or auto-installed database servers.
- No Windows Service support, no automatic updater, no code signing.
- Bundled secrets are not encrypted and can be extracted.
INITIAL_ADMIN.RESET_PASSWORD_IF_USER_EXISTSis not implemented.- Native Python packages may require manual
BUILD.HIDDEN_IMPORTSentries.
Example
See examples/basic_project for a complete
project demonstrating SQLite mode, .env.example,
ENVIRONMENT.INCLUDE, runtime settings integration, the Django admin
URL, STATIC_ROOT and CONSOLE=True.
Release files for django-binary-builder 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_binary_builder-0.1.2.tar.gz | 34.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_binary_builder-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 86.1 kB
Release files / django_binary_builder-0.1.2.tar.gz
| Download URL | django_binary_builder-0.1.2.tar.gz |
|---|---|
| Size | 34.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6224de7db631a60494a2c7ff1951cdf5c98ac0e77581214bb71a26b2e0b8b6ad
|
|
BLAKE2b-256 checksum How to use checksums |
e365fee2c6ec53a8ec4f17c09a00d7524ed441fdf2dca477744f7bde63c04a55
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / django_binary_builder-0.1.2-py3-none-any.whl
| Download URL | django_binary_builder-0.1.2-py3-none-any.whl |
|---|---|
| Size | 51.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f570805f35fd45a39c5d24ade0f03014059952187c289f6c1d3a4af925b5c9a7
|
|
BLAKE2b-256 checksum How to use checksums |
1ab7fc7b3e5224313ca0eb69c627485425b2da045ac7ac31d59409a368def153
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|