Encrypted environment variable manager โ set, read, and validate secrets with Fernet encryption.
Project description
๐ Environment Forge
Encrypted environment variable manager for Python.
Pure Python โข No framework dependencies โข Works everywhere
pip install environment-forge
Why Environment Forge?
| Problem | Solution |
|---|---|
.env files contain plaintext secrets |
Fernet-encrypted vault (AES-128-CBC + HMAC-SHA256) |
| Secrets leak through git, logs, env dumps | Single encrypted file โ unreadable without the key |
| Different config for Docker vs local | Auto-detects the vault in both environments |
| No validation until runtime crash | Schema validation with type casting at startup |
| Tied to one framework | Pure Python โ works with Django, FastAPI, Flask, or plain scripts |
Quick Start
1. Install
pip install environment-forge
# Optional: install with rich for beautiful CLI tables
pip install environment-forge[rich]
2. Initialise a vault
eforge init
This creates a .eforge/ directory in your project:
.eforge/
โโโ secret.key โ master key (auto-generated, chmod 600)
โโโ vault.enc โ encrypted variables
โโโ schema.json โ variable declarations (optional)
3. Store your secrets
eforge set SECRET_KEY "django-insecure-change-me"
eforge set DATABASE_HOST localhost
eforge set DATABASE_PORT 5432
eforge set DATABASE_PASSWORD supersecret
eforge set DEBUG true
4. Use in your project
# One line โ works in any Python project
import environment_forge
environment_forge.load()
# Now os.environ has all your vault values
import os
print(os.environ["SECRET_KEY"]) # "django-insecure-change-me"
print(os.environ["DATABASE_HOST"]) # "localhost"
That's it. No framework-specific setup. Pure Python.
Framework Integration
Django
# settings.py
import os
import environment_forge
# Load encrypted vault into os.environ BEFORE reading settings
environment_forge.load()
SECRET_KEY = os.environ["SECRET_KEY"]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": os.environ["DATABASE_HOST"],
"PORT": os.environ.get("DATABASE_PORT", "5432"),
"NAME": os.environ["DATABASE_NAME"],
"USER": os.environ["DATABASE_USER"],
"PASSWORD": os.environ["DATABASE_PASSWORD"],
}
}
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
FastAPI
# config.py
import os
import environment_forge
environment_forge.load()
DATABASE_URL = os.environ["DATABASE_URL"]
SECRET_KEY = os.environ["SECRET_KEY"]
DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
# main.py
from config import DATABASE_URL, SECRET_KEY
app = FastAPI()
Flask
# app.py
import os
import environment_forge
environment_forge.load()
app = Flask(__name__)
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URL"]
Plain Python / Scripts
import os
import environment_forge
environment_forge.load()
api_key = os.environ["API_KEY"]
Schema & Validation
Define what variables your project expects, then validate before your app starts.
Option A: CLI (no Python needed)
# Declare your schema
eforge schema add SECRET_KEY --sensitive --desc "Django secret key"
eforge schema add DATABASE_HOST --desc "Postgres host"
eforge schema add DATABASE_PORT --desc "Postgres port" --type int
eforge schema add DATABASE_PASSWORD --sensitive --desc "Postgres password"
eforge schema add DEBUG --optional --default false --type bool
# View it
eforge schema show
โญโโโโโโโโโโโโโโโโโโโโโ Environment Schema โโโโโโโโโโโโโโโโโโโโโโโฎ
โ # โ Key โ Required โ Type โ Default โ ๐ โ ... โ
โโโโโโผโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโผโโโโโโโโโโผโโโโโผโโโโโโค
โ 1 โ SECRET_KEY โ โ โ str โ โ โ ๐ โ โ
โ 2 โ DATABASE_HOST โ โ โ str โ โ โ โ โ โ
โ 3 โ DATABASE_PORT โ โ โ int โ โ โ โ โ โ
โ 4 โ DATABASE_PASSWORD โ โ โ str โ โ โ ๐ โ โ
โ 5 โ DEBUG โ โ โ bool โ false โ โ โ โ
โฐโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโดโโโโโโโโโโดโโโโโดโโโโโโฏ
# Validate the vault against the schema
eforge validate
โญโโโโโโโโโโโโโโโโ Validation Report โโโโโโโโโโโโโโโโโโฎ
โ Key โ Required โ Value โ Status โ
โโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโค
โ SECRET_KEY โ required โ djaโขโขโขโข-me โ โ set โ
โ DATABASE_HOST โ required โ localhost โ โ set โ
โ DATABASE_PORT โ required โ 5432 โ โ set โ
โ DATABASE_PASSWORD โ required โ supโขโขโขโขret โ โ set โ
โ DEBUG โ optional โ false โ โฉ dflt โ
โฐโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโฏ
โ All checks passed
Option B: Python code (for programmatic schemas)
from environment_forge import EnvSchema, EnvVar, load_and_validate
# Define schema as a list of EnvVar objects
SCHEMA = EnvSchema([
EnvVar("SECRET_KEY", required=True, sensitive=True, description="Django secret key"),
EnvVar("DATABASE_HOST", required=True, description="Postgres host"),
EnvVar("DATABASE_PORT", required=True, description="Postgres port", cast=int),
EnvVar("DATABASE_PASSWORD", required=True, sensitive=True, description="DB password"),
EnvVar("DEBUG", required=False, default="false", cast=bool, description="Debug mode"),
])
# Load vault + inject into os.environ + validate โ all in one call
result = load_and_validate(SCHEMA)
# result.resolved contains type-casted values:
# {
# "SECRET_KEY": "django-insecure-change-me",
# "DATABASE_HOST": "localhost",
# "DATABASE_PORT": 5432, โ int
# "DATABASE_PASSWORD": "supersecret",
# "DEBUG": False, โ bool
# }
If validation fails, you get a clear error and the process exits:
environment-forge: validation failed
Missing: REDIS_URL, API_KEY
Error: DATABASE_PORT: cannot cast 'abc' to int
CLI Reference
Vault Management
| Command | Description |
|---|---|
eforge init |
Create a new encrypted vault |
eforge set KEY VALUE [-s section] |
Store or update a value |
eforge get KEY [-s section] [--raw] |
Retrieve a value |
eforge delete KEY [-s section] |
Remove a key |
eforge list [-s section] |
List all keys in a section |
eforge status |
Overview of all sections with schema annotations |
eforge sections |
List all section names |
Import / Export
| Command | Description |
|---|---|
eforge import .env [-s section] |
Import from a .env file into the vault |
eforge export [-s section] [-o file] |
Export as .env format |
eforge inject [-s section] |
Print export statements for shell sourcing |
Schema & Validation
| Command | Description |
|---|---|
eforge schema add KEY [options] |
Declare an expected variable |
eforge schema remove KEY |
Remove a declaration |
eforge schema show |
Display the full schema |
eforge validate |
Validate vault against schema |
Schema add options
--optional Mark as optional (default: required)
--desc TEXT Description
--type TYPE Type: str, int, float, bool
--default VALUE Default value when not set
--sensitive Mask value in output (passwords, tokens)
-s SECTION Section (default: default)
Other
| Command | Description |
|---|---|
eforge destroy [-f] |
Delete vault and key from disk |
Global Options
eforge --vault /path/to/.eforge set KEY VALUE # custom vault location
Sections
Organise variables into logical groups:
eforge set HOST pg -s postgres
eforge set PORT 5432 -s postgres
eforge set URL redis://localhost -s redis
eforge list -s postgres # list only postgres keys
eforge export -s redis # export only redis section
eforge inject -s postgres # inject only postgres vars
In Python:
import environment_forge
# Inject only a specific section
environment_forge.load(section="postgres")
# Or inject everything
environment_forge.load()
Docker
Environment Forge keeps your encrypted vault outside the project directory using a Docker named volume. Secrets never live inside the application image.
Quick start
# 1. Build your vault locally
eforge init
eforge import .env
# 2. Copy vault to Docker volume path
eforge docker-init # copies .eforge โ /eforge (or --path /my/path)
Docker Compose (recommended)
services:
app:
build: .
volumes:
- eforge_data:/eforge # persistent named volume
environment:
- EFORGE_VAULT_PATH=/eforge # tell eforge where to look
volumes:
eforge_data: # Docker manages this volume
The vault is stored on a Docker named volume โ not in your source code, not baked into the image, and fully persistent across container restarts.
Dockerfile
FROM python:3.12-slim
WORKDIR /app
RUN pip install environment-forge
# Declare the volume mount point
VOLUME /eforge
COPY . .
# Option 1: Shell injection
CMD ["sh", "-c", "eval $(eforge inject) && python manage.py runserver"]
# Option 2: Python injection (recommended)
# Just call environment_forge.load() in your settings/config
CMD ["python", "manage.py", "runserver"]
Pre-populating the volume
# From your dev machine โ copy local vault into a running container's volume
docker cp .eforge/vault.enc mycontainer:/eforge/vault.enc
docker cp .eforge/secret.key mycontainer:/eforge/secret.key
# Or use eforge docker-init with --copy-from
eforge docker-init --copy-from .eforge --path /eforge
Using EFORGE_SECRET (no key file)
If you prefer not to store secret.key on the volume, pass the secret
via an environment variable:
services:
app:
build: .
volumes:
- eforge_data:/eforge
environment:
- EFORGE_VAULT_PATH=/eforge
- EFORGE_SECRET=${EFORGE_SECRET} # from .env or CI secrets
volumes:
eforge_data:
# Get your secret key value
cat .eforge/secret.key
# Pass it securely (e.g. from CI/CD secrets manager)
docker run -e EFORGE_SECRET=<your-secret> -v eforge_data:/eforge myapp
Auto-detection
environment_forge.load() finds the vault automatically:
EFORGE_VAULT_PATHenv var โ explicit override/eforgeโ Docker named volume mount/run/secrets/eforgeโ Docker secrets mount.eforge/in current directory โ local development
Python API Reference
environment_forge.load()
load(
section: str = None, # inject one section, or all if None
overwrite: bool = False, # overwrite existing os.environ keys?
vault_path: str = None, # explicit vault directory
validate: bool = False, # validate against schema.json if it exists
) -> Vault
environment_forge.load_and_validate()
load_and_validate(
schema: EnvSchema, # your schema object
section: str = None,
overwrite: bool = False,
vault_path: str = None,
) -> ValidationResult
Vault
vault = Vault() # or Vault(path="/custom/.eforge")
vault.set("KEY", "value") # store
vault.set("KEY", "value", section="db") # store in section
vault.get("KEY") # retrieve (None if missing)
vault.delete("KEY") # remove
vault.all() # dict of all keys in default section
vault.flat() # dict of all keys across all sections
vault.sections() # list of section names
vault.import_env(".env") # import from .env file
vault.export_env() # export as .env text
vault.inject() # inject into os.environ
vault.inject_all() # inject all sections
EnvSchema & EnvVar
schema = EnvSchema([
EnvVar("KEY", required=True, description="...", cast=int, sensitive=True, default="0", section="default"),
])
result = schema.validate(vault)
# result.valid โ bool
# result.resolved โ dict of type-casted values
# result.missing โ list of missing EnvVar objects
# result.errors โ list of error strings
# Save/load schema as JSON (used by CLI validate)
schema.save(".eforge/")
schema = EnvSchema.load(".eforge/")
Environment Variables
| Variable | Description |
|---|---|
EFORGE_SECRET |
Master secret (alternative to secret.key file) |
EFORGE_VAULT_PATH |
Path to vault directory (overrides auto-detection) |
Security
- Encryption: Fernet (AES-128-CBC + HMAC-SHA256) via
cryptography - Key derivation: SHA-256 hash โ 32-byte Fernet key
- File permissions:
secret.keyandvault.encโchmod 600 - Tamper detection: HMAC validated before decryption
- No plaintext on disk: All values in a single encrypted blob
- Only dependency:
cryptography(pure Python otherwise)
Best Practices
- Add
.eforge/to.gitignoreโ never commit secrets - Back up
secret.keyseparately โ without it, the vault is unrecoverable - Use
EFORGE_SECRETenv var in CI/CD pipelines - Use
eforge validatein your entrypoint to fail fast
Project Structure
environment-forge/
โโโ src/
โ โโโ environment_forge/
โ โโโ __init__.py # Public API: load(), load_and_validate()
โ โโโ cli.py # eforge CLI (rich optional)
โ โโโ crypto.py # Fernet encryption engine
โ โโโ vault.py # Encrypted key-value store
โ โโโ schema.py # EnvVar / EnvSchema / ValidationResult
โ โโโ loader.py # Framework-agnostic loader
โโโ tests/
โโโ pyproject.toml # pip install environment-forge
โโโ Makefile
โโโ LICENSE
โโโ README.md
Development
git clone https://github.com/tarnasi/eforge.git
cd eforge
make dev # install with dev + rich deps
make test # run tests
make build # build for PyPI
make publish # upload to PyPI
License
MIT โ see LICENSE.
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 environment_forge-0.0.3.tar.gz.
File metadata
- Download URL: environment_forge-0.0.3.tar.gz
- Upload date:
- Size: 29.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1dc1e5c6999278592e0c82942e3a5c1a7b69907f1324b8ff4d2da55d913721cd
|
|
| MD5 |
4cd1476c35228213450bce7480ceee93
|
|
| BLAKE2b-256 |
702a06fa2ea86b2be7ec215ffe275827a83ea9ace88faafbb377409205cab88e
|
File details
Details for the file environment_forge-0.0.3-py3-none-any.whl.
File metadata
- Download URL: environment_forge-0.0.3-py3-none-any.whl
- Upload date:
- Size: 22.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8cd0cf580507f1b81c49048711c0926c4f3f4305dab738af5485c6b53ff65c6f
|
|
| MD5 |
e8ff6f7676c83e35057459ecf8d44c91
|
|
| BLAKE2b-256 |
dcf059ceb9755a77692b58d640f4e3c3e78ec56aa4476cbab3628ccbf70976d4
|