Django + Bun deployment platform
Project description
djb - Django + Bun Platform
djb is a deployment platform for Django applications with frontend tooling (Bun). It provides utilities for secrets management, deployment, and development workflows.
djb structure:
djb/
├── src/djb/
│ ├── __init__.py # Package entry point (exports logging, config)
│ ├── types.py # Core types (Mode, Target enums)
│ ├── cli/ # Command-line interface
│ │ ├── djb.py # Main CLI entry point
│ │ ├── init.py # Environment initialization
│ │ ├── secrets.py # Secrets management commands
│ │ ├── deploy.py # Heroku deployment
│ │ ├── health.py # Health checks (lint, typecheck, test)
│ │ ├── db.py # Database operations
│ │ └── ... # More subcommands
│ ├── config/ # Configuration system
│ │ ├── __init__.py # Public API (config, configure, DjbConfig)
│ │ ├── config.py # DjbConfig class and lazy loader
│ │ └── fields.py # Config field definitions
│ ├── core/ # Core utilities
│ │ ├── __init__.py # Public API (exceptions, logging)
│ │ ├── exceptions.py# Exception hierarchy
│ │ └── logging.py # Logging with colored output
│ ├── secrets/ # Encrypted secrets management
│ │ ├── __init__.py # Public API exports
│ │ ├── core.py # SOPS encryption/decryption
│ │ └── gpg.py # GPG key protection
│ └── testing/ # Reusable test utilities
│ └── __init__.py # pytest hooks and fixtures
└── pyproject.toml
Installation
If you don't have uv installed yet:
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
Install djb as a dependency in your project:
# Add djb to your project
uv add djb
# Verify djb is available
djb --help
For local development of djb alongside your project:
# Clone djb into your project as a subdirectory
git clone https://github.com/kajicom/djb
# Install in editable mode
djb editable-djb
Configuration
djb uses a layered configuration system with four global settings:
| Setting | CLI Flag | Env Var | Config Key | Default |
|---|---|---|---|---|
| Project Name | --project-name |
DJB_PROJECT_NAME |
project_name |
pyproject.toml project.name |
| Mode | --mode |
DJB_MODE |
mode |
development |
| Target | --target |
DJB_TARGET |
target |
heroku |
| Project Dir | --project-dir |
DJB_PROJECT_DIR |
project_dir |
Current directory |
Resolution priority (highest to lowest):
- CLI flag
- Environment variable
.djb/local.toml(user overrides).djb/project.toml(shared)- Default value
Modes
development- Local development (default)staging- Staging environmentproduction- Production deployment
Mode affects which secrets are loaded during deployment and triggers safety guards:
# Deploy with production mode (recommended)
djb --mode production deploy heroku
# Mode persists to config when explicitly set
djb --mode production deploy heroku # Saves mode=production
djb deploy heroku # Uses saved production mode
Configuration Files
Configuration is stored in two files:
.djb/local.toml (user-specific, gitignored):
name = "Your Name"
email = "you@example.com"
mode = "production"
.djb/project.toml (shared, committed):
project_name = "myapp"
target = "heroku"
seed_command = "myapp.cli.seed:seed"
[k8s.domain_names."example.com"]
manager = "cloudflare"
Run djb init to set up configuration interactively.
Environment Variables
djb responds to these environment variables:
| Variable | Description | Default |
|---|---|---|
DJB_LOG_LEVEL |
Logging verbosity (error, warning, info, note, debug) |
info |
DJB_PROJECT_DIR |
Project root directory (overrides auto-detection) | Auto-detected |
DJB_MODE |
Deployment mode (development, staging, production) |
development |
DJB_TARGET |
Deployment target (currently only heroku) |
heroku |
DJB_PROJECT_NAME |
Project name (DNS-safe identifier) | From pyproject.toml or directory |
DJB_NAME |
User name for commits and identity | From git config user.name |
DJB_EMAIL |
User email for commits and identity | From git config user.email |
DJB_DOMAINS |
Comma-separated list of domains for ALLOWED_HOSTS |
From target config (heroku/k8s) |
DJB_SEED_COMMAND |
Seed command path (module:attribute) |
None |
Environment variables take precedence over config files but are overridden by CLI flags.
Features
Initialization
One-command setup for development environment:
# Full initialization
djb init
# Initialize with options
djb init --skip-brew # Skip Homebrew dependencies
djb init --skip-frontend # Skip frontend setup
djb init --skip-secrets # Skip secrets initialization
This installs:
- System dependencies via Homebrew (age, SOPS, PostgreSQL, GDAL, Bun)
- Python dependencies (
uv sync) - Frontend dependencies (
bun install) - Encrypted secrets management
Database Management
Manage PostgreSQL databases for development:
# Initialize development database (creates db, user, PostGIS)
djb db init
# Check database connection status
djb db status
Secrets Management
Age + SOPS encrypted secrets for secure configuration:
# Initialize project (creates .age/keys.txt, secrets, and config)
djb init
# Edit environment secrets
djb secrets edit dev
djb secrets edit production
# View secrets
djb secrets view dev
djb secrets list
# Backup private key to clipboard (store in password manager!)
djb secrets export-key | pbcopy
Each project has its own encryption key stored in .age/keys.txt.
Make sure to back up your key securely. If lost, you won't be able to decrypt existing secrets.
Copy your private Age key to the clipboard:
djb secrets export-key | pbcopy
Documentation: See docs/secrets-guide.md
How Secrets Encryption Works
djb uses a layered encryption approach:
- Age encryption encrypts the actual secrets (SOPS files)
- GPG encryption protects the Age private key at rest
- SOPS provides multi-recipient encryption for team collaboration
The encryption flow:
- Your Age private key is GPG-encrypted at
.age/keys.txt.gpg - When you run
djb secrets edit, the key is temporarily decrypted - SOPS uses your Age key to decrypt/encrypt the secrets file
- The Age key is immediately re-encrypted when done
Manual Recovery Operations
If djb commands fail, you can use these manual recovery operations:
Decrypt age key manually:
gpg --decrypt .age/keys.txt.gpg > .age/keys.txt
# Now you can use the plaintext key with SOPS
Encrypt age key manually:
gpg --encrypt --recipient your@email.com --armor --output .age/keys.txt.gpg .age/keys.txt
rm .age/keys.txt
Decrypt SOPS secrets manually:
SOPS_AGE_KEY_FILE=.age/keys.txt sops --decrypt secrets/development.yaml
Encrypt/edit SOPS secrets manually:
SOPS_AGE_KEY_FILE=.age/keys.txt sops secrets/development.yaml
Emergency Recovery
Lost age key: If you've lost your age private key and don't have a backup, the encrypted secrets are unrecoverable. Prevention:
- Store your age key in a password manager immediately after generation
- Use
djb secrets export-key | pbcopyto copy it to clipboard for backup
Corrupt .sops.yaml:
- Check git history:
git log -p secrets/.sops.yaml - Restore from git:
git checkout HEAD~1 -- secrets/.sops.yaml - Or regenerate with:
djb secrets rotate
GPG agent not responding: If GPG prompts hang or fail:
gpgconf --kill gpg-agent
gpgconf --launch gpg-agent
Team Member Onboarding
When a new team member joins:
- They run
djb initto generate their own age keypair - Their public key is added to
secrets/.sops.yaml - An existing team member runs
djb secrets rotateto re-encrypt project secrets - The new member can now decrypt staging/production secrets
When a team member leaves:
- Remove their public key from
secrets/.sops.yaml - Run
djb secrets rotateto re-encrypt with remaining keys - Consider rotating any secrets they had access to
Health Checks
Run linting, type checking, and tests for your project:
# Run all health checks (lint + typecheck + tests including E2E)
djb health
# Skip E2E tests
djb health --no-e2e
# Run specific checks
djb health lint # Run linting (black for backend, bun lint for frontend)
djb health lint --fix # Auto-fix lint issues
djb health typecheck # Run type checking (pyright for backend, tsc for frontend)
djb health test # Run tests including E2E (pytest for backend, bun test for frontend)
djb health test --no-e2e # Run tests without E2E
# Scope to backend or frontend only
djb health --backend # Backend checks only
djb health --frontend # Frontend checks only
djb health --backend typecheck # Backend type checking only
Code Coverage: Tests run with coverage enabled by default. Coverage reports show which lines are missing test coverage:
# Run tests with coverage (default)
djb health test
# Disable coverage for faster test runs
djb health test --no-cov
Coverage configuration is in pyproject.toml under [tool.coverage.*] sections. HTML reports are generated in htmlcov/.
Editable Mode Awareness: When djb is installed in editable mode (e.g., during development), health checks automatically run for both the djb package and the host project. When running from inside the djb directory, only djb is checked (host project is skipped).
Deployment
Heroku deployment with frontend builds, secrets sync, and migrations:
# Deploy to Heroku (uses project_name from config)
djb deploy heroku
# Deploy in production mode (recommended)
djb --mode production deploy heroku
# Or specify app explicitly
djb deploy heroku --app myapp
# Deploy with options
djb deploy heroku --local-build --skip-secrets
# Configure Heroku app (buildpacks, postgres, git remote)
djb deploy heroku setup
# Revert to previous deployment
djb deploy heroku revert
# Revert to specific commit
djb deploy heroku revert abc1234
Project Name: The Heroku app name is determined from:
--appCLI optionproject_namein.djb/project.yamlproject.nameinpyproject.toml
Run djb init to configure your project name.
Configuration Management
View and modify djb settings:
# Show current configuration
djb config --show
# Show configuration with source information
djb config --show --with-provenance
# Set configuration values
djb config seed_command myapp.cli.seed:seed
djb config project_name myapp
djb config hostname example.com
djb config name "Your Name"
djb config email you@example.com
# Remove a setting
djb config seed_command --delete
Configuration is stored in .djb/local.yaml (user-specific) and .djb/project.yaml (shared).
Additional Commands
Dependency Management:
# Refresh dependencies
djb --backend dependencies # Backend only
djb --frontend dependencies # Frontend only
djb --backend --frontend dependencies # Both
# Upgrade to latest versions
djb --backend dependencies --bump
Database Seeding:
# Run project seed command (configured via djb config seed_command)
djb seed
# Pass arguments to seed command
djb seed -- --fixtures users,products
Superuser Sync:
# Sync Django superuser from encrypted secrets
djb sync-superuser
# Sync from specific mode
djb --mode production sync-superuser
# Sync on Heroku
djb sync-superuser --app myapp
Publishing:
# Publish to PyPI (bump version, tag, push)
djb publish # Patch version (0.0.X)
djb publish --minor # Minor version (0.X.0)
djb publish --major # Major version (X.0.0)
djb publish --dry-run # Preview without changes
Editable Development:
# Install djb in editable mode
djb editable-djb
# Check current status
djb editable-djb --status
# Uninstall editable and use PyPI version
djb editable-djb --uninstall
Help:
# Show help for any command
djb --help
djb <command> --help
djb help
Usage
Command Line
Run djb commands with global options:
# Basic usage
djb <command>
# With mode (persists to config)
djb --mode production deploy heroku
# With logging
djb --log-level debug health
# Scope to backend/frontend
djb --backend health typecheck
djb --frontend health lint
# Quiet mode (suppress non-essential output)
djb -q deploy heroku
# Verbose mode (show detailed output)
djb -v init
Global flags: --mode, --target, --project-dir, --log-level, -q/--quiet, -v/--verbose, --backend, --frontend
Python API
Import djb modules directly in Python code:
from djb.secrets import load_secrets, load_secrets_for_mode
from djb.types import Mode
# Load secrets by environment name
secrets = load_secrets("production")
api_key = secrets['api_keys']['stripe']
# Load secrets by Mode enum (recommended for CLI integration)
secrets = load_secrets_for_mode(Mode.PRODUCTION)
# Get project name from config
from djb import config
project = config.project_name
Development
Running Tests
# Unit tests
uv run pytest
# Skip E2E tests (runs faster)
uv run pytest --no-e2e
# E2E tests only (requires GPG, age, SOPS, PostgreSQL)
uv run pytest --only-e2e tests/e2e/
# Specific E2E test file
uv run pytest tests/e2e/test_secrets.py -v
Prerequisites for E2E tests:
- GPG:
brew install gnupg - age:
brew install age - SOPS:
brew install sops - PostgreSQL:
brew install postgresql@17
Adding New Commands
- Create a new subcommand module in
src/djb/cli/ - Define your Click command group
- Register it in
src/djb/cli/djb.py:
from djb.cli.mycommand import mycommand
djb_cli.add_command(mycommand)
- Add E2E tests in
src/djb/cli/tests/e2e/test_<command>.py
Adding New Features
- Implement the feature in an appropriate module under
src/djb/ - Export public API in
src/djb/__init__.pyif needed - Add CLI commands if applicable
- Add E2E tests for CLI commands
- Update documentation
E2E Test Guidelines
E2E tests live in src/djb/cli/tests/e2e/ and use the @pytest.mark.e2e_marker marker. E2E tests run by default; use --no-e2e to skip them:
import pytest
pytestmark = pytest.mark.e2e_marker # Mark all tests in module as e2e
def test_my_command(runner, djb_pyproject_with_git):
result = runner.invoke(djb_cli, ["my-command"])
assert result.exit_code == 0
Key principles:
- Use real tools (GPG, age, SOPS, PostgreSQL)
- Mock cloud services (Heroku, PyPI)
- Isolate test environment using tool-specific flags (e.g.,
GNUPGHOME,SOPS_AGE_KEY_FILE) - Use
tmp_pathfor all file operations - Ensure error-safe encryption handling (always cleanup plaintext on failure)
See src/djb/cli/tests/e2e/conftest.py for shared fixtures and src/djb/cli/tests/e2e/utils.py for utilities.
Architecture Decisions
Why Integrated Development
djb can be embedded within projects as a subdirectory and installed in editable mode. This allows:
- Rapid iteration on both the platform and application
- Project-specific customization
- Simplified dependency management during development
For production deployments, djb is installed from PyPI.
Future Plans
Planned djb features:
- Environment initialization -
djb init - Deployment commands (Heroku) -
djb deploy heroku,djb deploy heroku revert - Heroku setup -
djb deploy heroku setup(buildpacks, postgres, git remote) - Project name auto-detection from config and pyproject.toml
- Global configuration (mode, target, project_name)
- Deployment guards (warns if not in production mode)
- Mode-based secrets loading
- Git hooks setup via
djb init(pre-commit hook for editable djb check) - Multi-recipient secret encryption
- Secret rotation automation
- Deployment commands (Kubernetes)
- Development server management
- Database migration utilities
- Environment variable syncing
References
- Secrets Guide - User guide for secrets management
- Age Encryption - Encryption specification
- Click - CLI framework
License
djb is licensed under the MIT License.
Mascot Attribution
The djb mascot (dj_bun) was created for this project and is distributed under CC BY-SA 4.0.
/dj_bun: playin' dev and deploy since 1984 🎶
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file djb-0.2.36.tar.gz.
File metadata
- Download URL: djb-0.2.36.tar.gz
- Upload date:
- Size: 477.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b307ea873b5de09f5b292b76379b5f2bef26b182c40434fea372896a1e71ff2
|
|
| MD5 |
f6d957f06064edd8658c1f9639f3e890
|
|
| BLAKE2b-256 |
370cff150d328bafa7d09c107c82605ae8a03a553cbece7eb0d104939a1bfe4b
|
Provenance
The following attestation bundles were made for djb-0.2.36.tar.gz:
Publisher:
publish.yaml on kajicom/djb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
djb-0.2.36.tar.gz -
Subject digest:
8b307ea873b5de09f5b292b76379b5f2bef26b182c40434fea372896a1e71ff2 - Sigstore transparency entry: 797475551
- Sigstore integration time:
-
Permalink:
kajicom/djb@d894a652b46dd7dff229ef0b57e99291cbf2d81c -
Branch / Tag:
refs/tags/v0.2.36 - Owner: https://github.com/kajicom
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@d894a652b46dd7dff229ef0b57e99291cbf2d81c -
Trigger Event:
push
-
Statement type:
File details
Details for the file djb-0.2.36-py3-none-any.whl.
File metadata
- Download URL: djb-0.2.36-py3-none-any.whl
- Upload date:
- Size: 596.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0536db77f190c85053abdb3117083aa1718c343d1f4c3e30c7cc4da8a30ad578
|
|
| MD5 |
87916eb241a655900625e60e44db7b8e
|
|
| BLAKE2b-256 |
8fbf22c11a0e179d6273b7aa08df2606f3dacdee671062cd52956f32a3546067
|
Provenance
The following attestation bundles were made for djb-0.2.36-py3-none-any.whl:
Publisher:
publish.yaml on kajicom/djb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
djb-0.2.36-py3-none-any.whl -
Subject digest:
0536db77f190c85053abdb3117083aa1718c343d1f4c3e30c7cc4da8a30ad578 - Sigstore transparency entry: 797475558
- Sigstore integration time:
-
Permalink:
kajicom/djb@d894a652b46dd7dff229ef0b57e99291cbf2d81c -
Branch / Tag:
refs/tags/v0.2.36 - Owner: https://github.com/kajicom
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@d894a652b46dd7dff229ef0b57e99291cbf2d81c -
Trigger Event:
push
-
Statement type: