KeyEnvKeyEnv
SDKs & Integrations

Django Integration

Use KeyEnv with Django applications for secure secrets management.

Django Integration

Integrate KeyEnv with your Django application to securely manage secrets like database credentials, API keys, and the Django secret key.

Features

  • Load secrets before Django initializes
  • Environment-aware configuration (development, staging, production)
  • Works with django-environ and python-decouple patterns
  • Supports DATABASE_URL parsing
  • Type-safe with full Python type annotations

Installation

pip install keyenv

Or with poetry:

poetry add keyenv

For DATABASE_URL parsing, also install dj-database-url:

pip install dj-database-url

Quick Start

Add KeyEnv to the top of your settings.py:

# settings.py
from keyenv import KeyEnv
import os

# Load secrets before anything else
if os.environ.get("KEYENV_TOKEN"):
    client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
    client.load_env(
        os.environ["KEYENV_PROJECT"],
        os.environ.get("DJANGO_ENV", "development")
    )

# Now use secrets in your settings
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"

Django Settings Integration

Basic Setup

The simplest approach loads all secrets into os.environ at startup:

# settings.py
from keyenv import KeyEnv
import os

# Load secrets from KeyEnv
if os.environ.get("KEYENV_TOKEN"):
    client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
    env = os.environ.get("DJANGO_ENV", "development")
    client.load_env(os.environ["KEYENV_PROJECT"], env)

# Django settings
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost").split(",")

# Database
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "HOST": os.environ.get("DB_HOST", "localhost"),
        "PORT": os.environ.get("DB_PORT", "5432"),
        "NAME": os.environ.get("DB_NAME", "myapp"),
        "USER": os.environ.get("DB_USER", "postgres"),
        "PASSWORD": os.environ.get("DB_PASSWORD", ""),
    }
}

# Email
EMAIL_HOST = os.environ.get("EMAIL_HOST", "smtp.gmail.com")
EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "")
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "")

With DATABASE_URL

Use dj-database-url for cleaner database configuration:

# settings.py
from keyenv import KeyEnv
import dj_database_url
import os

# Load secrets
if os.environ.get("KEYENV_TOKEN"):
    client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
    client.load_env(os.environ["KEYENV_PROJECT"], os.environ.get("DJANGO_ENV", "development"))

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]

# Parse DATABASE_URL automatically
DATABASES = {
    "default": dj_database_url.config(
        default="postgres://localhost/myapp",
        conn_max_age=600,
        conn_health_checks=True,
    )
}

Store your database URL in KeyEnv as:

DATABASE_URL=postgres://user:password@host:5432/dbname

Environment-Aware Configuration

Automatic Environment Detection

Detect the environment from various sources:

# settings.py
from keyenv import KeyEnv
import os

def get_environment():
    """Determine environment from various sources."""
    # Check explicit setting first
    if env := os.environ.get("DJANGO_ENV"):
        return env

    # Check common deployment indicators
    if os.environ.get("RAILWAY_ENVIRONMENT"):
        return os.environ["RAILWAY_ENVIRONMENT"]
    if os.environ.get("RENDER"):
        return "production"
    if os.environ.get("HEROKU_APP_NAME"):
        return "production"

    # Default to development
    return "development"

ENVIRONMENT = get_environment()

# Load environment-specific secrets
if os.environ.get("KEYENV_TOKEN"):
    client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
    client.load_env(os.environ["KEYENV_PROJECT"], ENVIRONMENT)

# Environment-specific settings
DEBUG = ENVIRONMENT == "development"

Split Settings Pattern

For larger projects, use environment-specific settings files:

# settings/base.py
from keyenv import KeyEnv
import os

ENVIRONMENT = os.environ.get("DJANGO_ENV", "development")

if os.environ.get("KEYENV_TOKEN"):
    client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
    client.load_env(os.environ["KEYENV_PROJECT"], ENVIRONMENT)

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]

# Common settings...
INSTALLED_APPS = [...]
MIDDLEWARE = [...]
# settings/development.py
from .base import *

DEBUG = True
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
# settings/production.py
from .base import *

DEBUG = False
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "").split(",")

# Production-specific settings
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

Using with django-environ

If you prefer django-environ's API:

# settings.py
from keyenv import KeyEnv
import environ
import os

# Load KeyEnv secrets first
if os.environ.get("KEYENV_TOKEN"):
    client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
    client.load_env(os.environ["KEYENV_PROJECT"], os.environ.get("DJANGO_ENV", "development"))

# Initialize django-environ (reads from os.environ)
env = environ.Env(
    DEBUG=(bool, False),
    ALLOWED_HOSTS=(list, ["localhost"]),
)

# Use django-environ's typed accessors
SECRET_KEY = env("DJANGO_SECRET_KEY")
DEBUG = env("DEBUG")
ALLOWED_HOSTS = env("ALLOWED_HOSTS")

DATABASES = {
    "default": env.db("DATABASE_URL", default="postgres://localhost/myapp")
}

EMAIL_CONFIG = env.email_url("EMAIL_URL", default="smtp://localhost:25")

Using with python-decouple

If you prefer python-decouple's pattern:

# settings.py
from keyenv import KeyEnv
from decouple import config, Csv
import os

# Load KeyEnv secrets first
if os.environ.get("KEYENV_TOKEN"):
    client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
    client.load_env(os.environ["KEYENV_PROJECT"], os.environ.get("DJANGO_ENV", "development"))

# python-decouple reads from os.environ (and .env as fallback)
SECRET_KEY = config("DJANGO_SECRET_KEY")
DEBUG = config("DEBUG", default=False, cast=bool)
ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="localhost", cast=Csv())

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "HOST": config("DB_HOST", default="localhost"),
        "PORT": config("DB_PORT", default=5432, cast=int),
        "NAME": config("DB_NAME", default="myapp"),
        "USER": config("DB_USER", default="postgres"),
        "PASSWORD": config("DB_PASSWORD", default=""),
    }
}

Complete Example

Here's a complete settings.py for a production Django application:

# settings.py
from keyenv import KeyEnv
from pathlib import Path
import dj_database_url
import os

# Build paths
BASE_DIR = Path(__file__).resolve().parent.parent

# Determine environment
ENVIRONMENT = os.environ.get("DJANGO_ENV", "development")

# Load secrets from KeyEnv
if token := os.environ.get("KEYENV_TOKEN"):
    client = KeyEnv(token=token)
    project_id = os.environ.get("KEYENV_PROJECT")
    if project_id:
        client.load_env(project_id, ENVIRONMENT)

# Core settings
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost").split(",")

# Application definition
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    # Your apps
    "myapp",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    "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 = "myproject.urls"
WSGI_APPLICATION = "myproject.wsgi.application"

# Database
DATABASES = {
    "default": dj_database_url.config(
        default="postgres://localhost/myapp",
        conn_max_age=600,
        conn_health_checks=True,
    )
}

# Cache (optional)
if redis_url := os.environ.get("REDIS_URL"):
    CACHES = {
        "default": {
            "BACKEND": "django.core.cache.backends.redis.RedisCache",
            "LOCATION": redis_url,
        }
    }

# Email
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = os.environ.get("EMAIL_HOST", "smtp.gmail.com")
EMAIL_PORT = int(os.environ.get("EMAIL_PORT", "587"))
EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "true").lower() == "true"
EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "")
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "")
DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", "[email protected]")

# Security (production)
if ENVIRONMENT == "production":
    SECURE_SSL_REDIRECT = True
    SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True
    SECURE_HSTS_SECONDS = 31536000
    SECURE_HSTS_INCLUDE_SUBDOMAINS = True
    SECURE_HSTS_PRELOAD = True

# Static files
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"

# Third-party services
STRIPE_SECRET_KEY = os.environ.get("STRIPE_SECRET_KEY", "")
STRIPE_WEBHOOK_SECRET = os.environ.get("STRIPE_WEBHOOK_SECRET", "")
SENTRY_DSN = os.environ.get("SENTRY_DSN", "")

# Initialize Sentry
if SENTRY_DSN:
    import sentry_sdk
    sentry_sdk.init(
        dsn=SENTRY_DSN,
        environment=ENVIRONMENT,
        traces_sample_rate=0.1,
    )

Setting Up Your Deployment

Required Environment Variables

Set these in your deployment platform (not in KeyEnv):

VariableDescription
KEYENV_TOKENYour KeyEnv service token
KEYENV_PROJECTYour KeyEnv project ID
DJANGO_ENVEnvironment name (development, staging, production)

Secrets to Store in KeyEnv

Store these secrets in KeyEnv for each environment:

SecretDescriptionExample
DJANGO_SECRET_KEYDjango secret keydjango-insecure-abc123...
DATABASE_URLDatabase connection stringpostgres://user:pass@host/db
REDIS_URLRedis connection (optional)redis://localhost:6379/0
EMAIL_HOST_PASSWORDSMTP passwordapp-specific-password
STRIPE_SECRET_KEYStripe API keysk_live_...
SENTRY_DSNSentry DSNhttps://[email protected]/...
ALLOWED_HOSTSComma-separated hostsexample.com,www.example.com

Generate a Django Secret Key

python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

Local Development

For local development without KeyEnv:

# settings.py - handles missing KEYENV_TOKEN gracefully
if os.environ.get("KEYENV_TOKEN"):
    client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
    client.load_env(os.environ["KEYENV_PROJECT"], "development")

# Falls back to local .env file or defaults
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "dev-secret-key-not-for-production")

Or use the KeyEnv CLI to generate a .env file:

keyenv pull --env development --output .env

Then load it with python-dotenv in development:

# settings.py
import os

if os.environ.get("KEYENV_TOKEN"):
    from keyenv import KeyEnv
    client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
    client.load_env(os.environ["KEYENV_PROJECT"], os.environ.get("DJANGO_ENV", "development"))
elif os.path.exists(".env"):
    from dotenv import load_dotenv
    load_dotenv()

wsgi.py and asgi.py

No changes needed to wsgi.py or asgi.py. Secrets are loaded when settings.py is imported.

Testing

For tests, you can skip KeyEnv by not setting KEYENV_TOKEN:

# conftest.py (pytest)
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")

# Test-specific secrets
os.environ["DJANGO_SECRET_KEY"] = "test-secret-key"
os.environ["DATABASE_URL"] = "postgres://localhost/test_db"

Or use pytest-env:

# pytest.ini
[pytest]
env =
    DJANGO_SECRET_KEY=test-secret-key
    DATABASE_URL=postgres://localhost/test_db

Troubleshooting

"KEYENV_TOKEN not set"

In production, ensure KEYENV_TOKEN is set in your deployment platform's environment variables (Heroku Config Vars, Railway Variables, etc.).

"Project not found"

Verify KEYENV_PROJECT matches your project ID in the KeyEnv dashboard.

"Environment not found"

Check that the environment name (development, staging, production) exists in your KeyEnv project.

Secrets not loading

  1. Check that load_env() is called before accessing os.environ
  2. Verify the service token has read access to the environment
  3. Check for typos in secret key names

Performance

load_env() makes a single API call at startup. Secrets are cached in os.environ for the lifetime of the process. There's no runtime overhead after initialization.

On this page