Loading Secrets
How to initialize Redenv and load secrets into your application.
The Redenv client provides two methods for loading secrets: init() and load().
| Method | Returns | Use Where |
|---|---|---|
init() | Secrets | Only in config/redenv.py (app startup) |
load() | Secrets | Everywhere else (programmatic access) |
Both methods fetch, decrypt, cache secrets, and populate os.environ.
Info
Fun fact: init() and load() are identical under the hood — both return
Secrets. The different names make your code's intent clearer: "initialize at
startup" vs "load secrets for use".
Recommended Setup#
Step 1: Initialize and Validate at Startup#
Call init() only in the file where you create the client. Since init() returns the Secrets object, you can validate required secrets immediately:
import os
from redenv import Redenv
redenv = Redenv({
"project": os.environ["REDENV_PROJECT"],
"token_id": os.environ["REDENV_TOKEN_ID"],
"token": os.environ["REDENV_TOKEN_KEY"],
"upstash": {
"url": os.environ["UPSTASH_REDIS_URL"],
"token": os.environ["UPSTASH_REDIS_TOKEN"],
},
"environment": os.getenv("ENVIRONMENT", "development"),
})
async def initialize():
# Initialize and validate at startup
secrets = await redenv.init()
secrets.require("DATABASE_URL", "JWT_SECRET", "STRIPE_KEY")
return secretsimport os
from redenv import RedenvSync
redenv = RedenvSync({
"project": os.environ["REDENV_PROJECT"],
"token_id": os.environ["REDENV_TOKEN_ID"],
"token": os.environ["REDENV_TOKEN_KEY"],
"upstash": {
"url": os.environ["UPSTASH_REDIS_URL"],
"token": os.environ["UPSTASH_REDIS_TOKEN"],
},
"environment": os.getenv("ENVIRONMENT", "development"),
})
# Initialize and validate at startup
secrets = redenv.init()
secrets.require("DATABASE_URL", "JWT_SECRET", "STRIPE_KEY") Tip
Fail fast! By validating in config/redenv.py, your app crashes
immediately on startup if secrets are missing — not later when a route tries
to use them.
Step 2: Use load() Everywhere Else#
In all other files, use load() to get programmatic access to secrets:
from config.redenv import redenv
async def connect_database():
secrets = await redenv.load()
# Programmatic access (recommended)
db_url = secrets["DATABASE_URL"]
port = secrets.get("PORT", 5432, cast=int)
# Validate required secrets
secrets.require("DATABASE_URL", "DATABASE_PASSWORD")from config.redenv import redenv
def connect_database():
secrets = redenv.load()
# Programmatic access (recommended)
db_url = secrets["DATABASE_URL"]
port = secrets.get("PORT", 5432, cast=int)
# Validate required secrets
secrets.require("DATABASE_URL", "DATABASE_PASSWORD")Info
load() is idempotent. Calling it multiple times won't cause extra network
requests—it serves from cache within the configured TTL.
Why Programmatic Access?#
We highly recommend using load() over direct os.environ access:
| Feature | os.environ | secrets object |
|---|---|---|
| Type casting | ✘ Manual parsing | ✔ cast=int, cast=bool, etc. |
| Validation | ✘ Runtime errors | ✔ .require() fails fast |
| Scoping | ✘ Manual filtering | ✔ .scope("AWS_") |
| Masking | ✘ Exposed in logs | ✔ Auto-masked |
| Raw access | ✘ | ✔ .raw for unexpanded values |
secrets = await redenv.load()
# Type-safe casting
port = secrets.get("PORT", 3000, cast=int)
debug = secrets.get("DEBUG", cast=bool)
config = secrets.get("APP_CONFIG", cast=dict)
# Fail fast if required secrets are missing
secrets.require("DATABASE_URL", "STRIPE_KEY", "JWT_SECRET")
# Scoped access for modules
aws_config = secrets.scope("AWS_") # AWS_KEY → KEYsecrets = redenv.load()
# Type-safe casting
port = secrets.get("PORT", 3000, cast=int)
debug = secrets.get("DEBUG", cast=bool)
config = secrets.get("APP_CONFIG", cast=dict)
# Fail fast if required secrets are missing
secrets.require("DATABASE_URL", "STRIPE_KEY", "JWT_SECRET")
# Scoped access for modules
aws_config = secrets.scope("AWS_") # AWS_KEY → KEYServerless Functions#
For serverless environments (AWS Lambda), you can skip init() and use load() directly:
from config.redenv import redenv
async def handler(event, context):
# First call fetches from Redis, subsequent calls use cache
secrets = await redenv.load()
secrets.require("API_KEY")
return {"statusCode": 200, "body": "ok"}from config.redenv import redenv
def handler(event, context):
# First call fetches from Redis, subsequent calls use cache
secrets = redenv.load()
secrets.require("API_KEY")
return {"statusCode": 200, "body": "ok"}Info
In serverless, load() handles both initialization and access. The first
invocation warms the cache; subsequent calls in the same instance are instant.
Environment Population#
By default, both init() and load() populate os.environ:
import os
secrets = redenv.load() # or await redenv.load()
# These are equivalent:
secrets["API_KEY"] == os.environ["API_KEY"] # TrueControlling Override Behavior#
redenv = Redenv({
# ...
"env": {
"override": False, # Preserve existing env vars (useful for local .env overrides)
},
})Multiple Environments#
import os
from redenv import Redenv
base_options = {
"project": os.environ["REDENV_PROJECT"],
"token_id": os.environ["REDENV_TOKEN_ID"],
"token": os.environ["REDENV_TOKEN_KEY"],
"upstash": {
"url": os.environ["UPSTASH_REDIS_URL"],
"token": os.environ["UPSTASH_REDIS_TOKEN"],
},
}
prod_secrets = Redenv({
**base_options,
"environment": "production",
})
staging_secrets = Redenv({
**base_options,
"environment": "staging",
})Warning
Multiple environments will both try to populate os.environ. Use env: { "override": False } or rely solely on programmatic access.