Oct 27, 20265 min read

How to Add Secrets to a Django App

Keeping SECRET_KEY and API keys out of Django settings with Encore, django-environ, AWS Secrets Manager or Google Secret Manager, with local values and per-environment storage.

Django needs at least one secret, SECRET_KEY, and most projects add API keys for email, payments and storage. The default settings.py writes the key in plain text, and the usual fix moves it to environment variables, which then have to be stored and set for each environment.

This guide moves Django secrets out of the code with Encore, django-environ, AWS Secrets Manager and Google Secret Manager. Each version loads SECRET_KEY and a SendGrid API key.

What you need to decide:

  • Where encrypted values are stored for each environment.
  • How developers get local values.
  • Which code can read each secret.
  • How you rotate SECRET_KEY without logging everyone out.

Secrets options for Django at a glance

CapabilityEncoredjango-environAWS Secrets ManagerGoogle Secret Manager
Declared inPython codesettings.py reads env varsAWS console or IaCGCP console or IaC
Local values.secrets.user.toml.env fileReal secret or env varsReal secret or env vars
Production storageSecrets Manager or Secret Manager in your accountWhatever sets env varsAWSGCP
ScopeShared or per serviceWhole processIAM policyIAM binding
Setting a valueencore secret set --type productionPlatform env settingsConsole, CLI or IaCConsole, CLI or IaC

Encore: secrets for your existing Django project

Encore

Encore combines secrets declared in your Django code with per-environment storage, reading local values from a git-ignored file under encore run and keeping production values in AWS Secrets Manager or GCP Secret Manager in your own account. It is an infrastructure SDK added to the project, so apps, models and middleware stay as they are.

Encore declares infrastructure in Python code, runs it locally and provisions it in your AWS or GCP account
Infrastructure declared in Python, running locally under encore run and provisioned in your own AWS or GCP account on deploy.

The secrets are declared in notes/resources.py next to the database. SECRET_KEY is shared across services, and the SendGrid key belongs to this service:

from encore import secrets from encore.storage import postgres db = postgres.Database("notes") # Shared by every service; read once in settings.py. django_secret_key = secrets.SharedSecret[str]("DjangoSecretKey") # Private to this service. sendgrid_api_key = secrets.Secret[str]("SendgridApiKey")

settings.py reads the key at startup and builds DATABASES from the declared database:

from psycopg.conninfo import conninfo_to_dict from notes.resources import db, django_secret_key SECRET_KEY = django_secret_key.value DEBUG = False connection = conninfo_to_dict(db.connection_string) DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": connection.pop("dbname"), "USER": connection.pop("user", ""), "PASSWORD": connection.pop("password", ""), "HOST": connection.pop("host", ""), "PORT": connection.pop("port", ""), "OPTIONS": connection, } }

Views read the service secret where they use it. This one reports whether email is configured without revealing the key:

from django.http import HttpRequest, JsonResponse from django.views.decorators.http import require_GET from .resources import sendgrid_api_key @require_GET def mail_status(request: HttpRequest) -> JsonResponse: # Read the value where it is used, so a rotated key is picked up. key = sendgrid_api_key.value return JsonResponse({"configured": key.startswith("SG.")})

Local values go in .secrets.user.toml, which stays out of git:

[shared] DjangoSecretKey = "local-dev-only-key-0123456789abcdef0123456789abcdef" [services.notes] SendgridApiKey = "SG.local"

For production, generate a key with Python's secrets module and set it once per environment type:

~/notes
$ python -c "import secrets; print(secrets.token_urlsafe(50))"$ encore secret set --type production DjangoSecretKey$ encore secret set --type production SendgridApiKey$ encore run$ curl localhost:4000/mail/status/{"configured": true}

On deploy, the values live in Secrets Manager on AWS or Secret Manager on GCP in your own account, and Encore provisions an IAM role for the service. Preview environments use their own values, so a pull request never runs with production keys.

When an agent adds a new integration, it declares the secret beside the code that uses it and sets a local value, and the reviewer sees the new secret in the diff.

Good to know

Rotate SECRET_KEY by moving the old value into SECRET_KEY_FALLBACKS for a while, as the settings reference describes. Keep DEBUG = False outside development, because the debug page can print settings.

Try Encore →

django-environ

django-environ reads typed values from environment variables and a .env file:

import environ env = environ.Env(DEBUG=(bool, False)) environ.Env.read_env(".env") SECRET_KEY = env("DJANGO_SECRET_KEY") SENDGRID_API_KEY = env("SENDGRID_API_KEY") DATABASES = {"default": env.db("DATABASE_URL")}

Good to know

The .env file stays out of git, and each developer keeps their own. In production, the platform has to set the variables, so the encrypted storage and who can read it depend on where you host.

AWS Secrets Manager

On AWS, settings can fetch one JSON secret at startup with boto3:

import json import boto3 secret = boto3.client("secretsmanager").get_secret_value(SecretId="notes/production") values = json.loads(secret["SecretString"]) SECRET_KEY = values["DJANGO_SECRET_KEY"] SENDGRID_API_KEY = values["SENDGRID_API_KEY"]

Good to know

Settings now make a network call at import time, including for manage.py commands, so local development needs AWS credentials or a fallback. You create one secret per environment and grant the task role secretsmanager:GetSecretValue on it.

Google Secret Manager

On GCP, Secret Manager values can be mounted into Cloud Run as environment variables, so settings only read os.environ:

printf %s "$KEY" | gcloud secrets create django-secret-key --data-file=- printf %s "$SENDGRID_KEY" | gcloud secrets create sendgrid-api-key --data-file=- for s in django-secret-key sendgrid-api-key; do gcloud secrets add-iam-policy-binding "$s" \ --member="serviceAccount:notes@$PROJECT.iam.gserviceaccount.com" \ --role=roles/secretmanager.secretAccessor done gcloud run deploy notes --source . \ --service-account="notes@$PROJECT.iam.gserviceaccount.com" \ --set-secrets=DJANGO_SECRET_KEY=django-secret-key:latest,SENDGRID_API_KEY=sendgrid-api-key:latest

Good to know

Each secret needs its own IAM binding for the service account, and the service account must exist before the deploy. Local development uses a separate mechanism such as a .env file.

How to choose

  • Encore if you want Django secrets declared in code, with local values in a git-ignored file and production values in your own AWS or GCP account.
  • django-environ if your platform already stores and injects environment variables.
  • AWS Secrets Manager or Google Secret Manager directly if you manage the secrets and IAM bindings per environment yourself.

Python support in Encore is in beta, and you can sign up for access to try it with your Django project.

Frequently asked questions

How do I keep Django's SECRET_KEY out of settings.py?

Load it from a secret store at startup instead of writing it in the file. With Encore, declare django_secret_key = secrets.SharedSecret[str]("DjangoSecretKey") and set SECRET_KEY = django_secret_key.value in settings.py. Local values come from .secrets.user.toml and production values from encore secret set.

How do I generate a new Django SECRET_KEY?

Use django.core.management.utils.get_random_secret_key(), or Python's secrets.token_urlsafe(50). Changing the key logs out users and invalidates password reset links, so plan rotation with SECRET_KEY_FALLBACKS.

Should Django read secrets from environment variables?

Environment variables are a common delivery mechanism, but something still has to store the values encrypted and set them per environment. Libraries like django-environ read them; they do not store them.

How do I rotate Django's SECRET_KEY without logging everyone out?

Set the new value as SECRET_KEY and move the old one into SECRET_KEY_FALLBACKS. Django then accepts signatures made with either key until you remove the fallback.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders