Encrypted environment configuration cascade manager for .env files
Project description
dotconfig
Environment configuration cascade manager for .env files.
dotconfig assembles a single .env file from multiple layered source
files — public config, SOPS-encrypted secrets, and per-developer local
overrides — and can round-trip it back. It is designed for teams where:
- Different developers have different local settings (Docker context, domain names, key paths).
- Secrets live in SOPS-encrypted files alongside public config.
- Multiple named environments (dev, prod, test, staging, CI, …) share the same layout.
- Every tool (Docker, dotenv, IDEs) still reads a single
.envfile.
Table of Contents
- Installation
- Quick start
- Directory structure
- Generated
.envformat - Commands
- SOPS integration
- Workflow
- Adding a new deployment
- Adding a new developer
- Design decisions
Installation
Install with pipx so the tool is globally available without polluting any project's virtual environment:
pipx install edotconfig
Or into a project's own virtual environment:
pip install edotconfig
Verify the install:
dotconfig --version
Quick start
your-project/
config/
sops.yaml ← SOPS encryption rules (generated by dotconfig init)
dev/
public.env ← create this (public dev vars)
secrets.env ← SOPS-encrypted dev secrets
prod/
public.env ← create this (public prod vars)
secrets.env ← SOPS-encrypted prod secrets
local/
yourname/
public.env ← create this (your personal overrides)
secrets.env ← optional personal encrypted secrets
.env ← generated; do not edit directly (add to .gitignore)
Load config into .env:
dotconfig load -d dev -l yourname # dev deployment + your local overrides
dotconfig load -d prod # prod deployment, no local overrides
Edit .env directly if you need to tweak a value, then save it
back to the source files:
dotconfig save
Load/save specific files (YAML, JSON, etc.):
dotconfig load -d dev --file app.yaml --stdout # print to stdout
dotconfig save -d dev --file app.yaml # store into config/dev/
Directory structure
config/
sops.yaml # SOPS encryption rules (non-dotfile, in config/)
dev/
public.env # Public config for the "dev" deployment
secrets.env # SOPS-encrypted secrets for "dev"
prod/
public.env # Public config for the "prod" deployment
secrets.env # SOPS-encrypted secrets for "prod"
local/
alice/
public.env # Public local overrides for Alice
secrets.env # SOPS-encrypted local secrets (optional)
bob/
public.env # Another developer's overrides
Deployment names are open-ended — use any string that works as a directory
name (dev, prod, test, staging, ci, …).
Generated .env format
dotconfig load produces a .env file with marked sections that map
back to the source files:
# CONFIG_DEPLOY=dev
# CONFIG_LOCAL=ericbusboom
#@dotconfig: public (dev)
APP_DOMAIN=inventory.example.com
NODE_ENV=development
PORT=3000
DEPLOYMENT=dev
DATABASE_URL=postgresql://app:devpassword@localhost:5433/app
DO_SPACES_ENDPOINT=https://sfo3.digitaloceanspaces.com
DO_SPACES_BUCKET=my-bucket
DO_SPACES_REGION=sfo3
#@dotconfig: secrets (dev)
SESSION_SECRET=abc123...
GITHUB_CLIENT_ID=...
GOOGLE_CLIENT_ID=...
#@dotconfig: public-local (ericbusboom)
DEV_DOCKER_CONTEXT=orbstack
PROD_DOCKER_CONTEXT=swarm1
QR_DOMAIN=http://192.168.1.40:5173/
SOPS_AGE_KEY_FILE=/Users/ericbusboom/.config/sops/age/keys.txt
#@dotconfig: secrets-local (ericbusboom)
Last-write-wins: when the file is shell-sourced (set -a; . .env; set +a),
later sections override earlier ones. Local overrides deployment; secrets
override public.
The #@dotconfig: markers are unique to dotconfig — do not use this prefix
in your own comments. The two metadata comments (CONFIG_DEPLOY,
CONFIG_LOCAL) tell dotconfig save where to write each section back.
Commands
dotconfig init
Usage: dotconfig init [OPTIONS]
Initialise the config directory structure and set up age encryption.
Options:
--config-dir TEXT Root config directory to create. [default: config]
--help Show this message and exit.
Creates config/, config/local/, empty env files for dev/prod
deployments and the current user, and configures SOPS age encryption.
Safe to run multiple times.
Examples:
dotconfig init
dotconfig init --config-dir myconfig
dotconfig load
Usage: dotconfig load [OPTIONS]
Assemble config files into .env, or load a specific file.
Options:
-d, --deploy TEXT Deployment / environment name (e.g. dev, prod).
-l, --local TEXT Local / developer name for personal overrides.
-c, --config-dir TEXT Root config directory. [default: config]
-o, --output TEXT Destination file. [default: .env or the --file name]
-f, --file TEXT Load a specific file instead of assembling .env.
--stdout Print to stdout instead of writing to a file.
--help Show this message and exit.
Examples:
# Load dev deployment with Eric's local overrides
dotconfig load -d dev -l ericbusboom
# Load prod deployment, no local overrides
dotconfig load -d prod
# Write to a file other than .env
dotconfig load -d dev -o .env.dev
# Load a specific YAML file from the dev deployment
dotconfig load -d dev --file app.yaml
# Print a file to stdout (useful for agents / piping)
dotconfig load -d dev --file app.yaml --stdout
When using --file, specify either -d or -l (not both) — the file
lives in one location only.
What it reads (without --file):
| Source file | Section in .env |
|---|---|
config/{deploy}/public.env |
#@dotconfig: public ({deploy}) |
config/{deploy}/secrets.env (SOPS-encrypted) |
#@dotconfig: secrets ({deploy}) |
config/local/{local}/public.env |
#@dotconfig: public-local ({local}) |
config/local/{local}/secrets.env (SOPS-encrypted) |
#@dotconfig: secrets-local ({local}) |
If a secrets file is absent or SOPS is unavailable, the section is written as empty with a warning — the command does not abort.
If a local file is absent, a warning is printed and the section is written as empty — useful when a new developer clones the repo before creating their own local overrides.
dotconfig save
Usage: dotconfig save [OPTIONS]
Save .env sections back to config/ source files, or store a file.
Options:
-d, --deploy TEXT Target deployment (overrides .env metadata).
-l, --local TEXT Target local / developer name (overrides .env metadata).
--env-file TEXT .env file to read and save. [default: .env]
-c, --config-dir TEXT Root config directory. [default: config]
-f, --file TEXT Save a specific file into the config directory.
-e, --encrypt Encrypt the file with SOPS (only with --file).
--help Show this message and exit.
Examples:
# Save all sections from .env back to config/
dotconfig save
# Save to a different deployment
dotconfig save -d staging
# Save a YAML file into the dev deployment
dotconfig save -d dev --file app.yaml
# Save and encrypt a file with SOPS
dotconfig save --file secrets.yaml -d dev --encrypt
# Save a JSON file into a local config directory
dotconfig save -l alice --file settings.json
When using --file, specify either -d or -l (not both) — the file
lives in one location only.
What it writes (without --file):
Section in .env |
Destination file |
|---|---|
#@dotconfig: public ({deploy}) |
config/{deploy}/public.env (plaintext) |
#@dotconfig: secrets ({deploy}) |
config/{deploy}/secrets.env (SOPS-encrypted) |
#@dotconfig: public-local ({local}) |
config/local/{local}/public.env (plaintext) |
#@dotconfig: secrets-local ({local}) |
config/local/{local}/secrets.env (SOPS-encrypted, only if non-empty) |
dotconfig save requires a dotconfig-managed .env (one that was produced
by dotconfig load) because it relies on the CONFIG_DEPLOY metadata
comment to know where to write the files back.
dotconfig keys
Usage: dotconfig keys [OPTIONS]
Show age encryption key status and configuration.
Options:
--help Show this message and exit.
Reports where your age private key lives, the derived public key, and environment variable export statements for configuring SOPS.
dotconfig audit
Usage: dotconfig audit [OPTIONS]
Scan config/ for unencrypted secrets at rest.
Options:
-c, --config-dir TEXT Root config directory. [default: auto-discovered or 'config']
--help Show this message and exit.
Walks the config directory looking for values whose key names suggest they are secrets but are stored in plaintext rather than SOPS-encrypted. Exits with code 0 if clean, code 1 if findings exist (useful for CI and git hooks).
Examples:
dotconfig audit
dotconfig audit -c /path/to/config
dotconfig config
Usage: dotconfig config [OPTIONS]
Show dotconfig configuration and discovered paths.
Options:
--help Show this message and exit.
Reports the installed version, config directory name (from DOTCONFIG_NAME
or the default config), and where the config directory was found.
dotconfig install-hooks
Usage: dotconfig install-hooks [OPTIONS]
Install a git pre-commit hook that runs dotconfig audit.
Options:
--help Show this message and exit.
Installs a pre-commit hook that blocks commits when unencrypted secrets are detected in the config directory. Safe to run multiple times.
dotconfig agent
Usage: dotconfig agent [OPTIONS]
Print full operational instructions for AI agents.
Options:
--help Show this message and exit.
Outputs a comprehensive markdown document describing how dotconfig works,
all commands, the directory layout, the .env format, and rules for agents.
SOPS integration
dotconfig delegates all encryption and decryption to
sops. You must install sops separately.
SOPS is optional for loading public config. If sops is not installed, or a secrets file is missing, the secrets section is left empty and a warning is printed.
Key discovery follows the standard sops precedence:
SOPS_AGE_KEY_FILEenvironment variable (path to an age private key file)SOPS_AGE_KEYenvironment variable (inline age private key)sops.yamlspecified via--configflag orSOPS_CONFIGenvironment variable
If SOPS_AGE_KEY_FILE is defined inside .env itself (e.g. in the
public-local section), dotconfig save reads it from the file before
invoking sops, so you do not need to export it manually.
Recommended config/sops.yaml:
creation_rules:
# Secrets companion files (app.secrets.yaml, etc.)
- path_regex: '.+\.secrets\.(?:env|json|yaml|yml|txt|conf)$'
age: >-
age1v3f2rn...,age1h02a69...
# Legacy secrets files (secrets.env, etc.)
- path_regex: '.+/secrets\.(?:env|json|yaml|yml|txt|conf)$'
age: >-
age1v3f2rn...,age1h02a69...
# Catch-all for any file dotconfig encrypts (private keys, etc.)
- path_regex: '.+'
age: >-
age1v3f2rn...,age1h02a69...
Because sops.yaml is not a dotfile, SOPS will not auto-discover it.
Specify it explicitly when invoking sops directly:
SOPS_CONFIG=config/sops.yaml sops --encrypt --in-place config/dev/secrets.env
# or
sops --config config/sops.yaml --encrypt --in-place config/dev/secrets.env
Workflow
First-time setup (new developer)
git clone <repo>
# Create your personal local overrides
cp -r config/local/ericbusboom config/local/yourname
# Edit it with your values
$EDITOR config/local/yourname/public.env
# Load dev config
dotconfig load -d dev -l yourname
Daily development
# Reload if someone changed config files
dotconfig load -d dev -l yourname
# Make an ad-hoc change in .env directly, then save it back
$EDITOR .env
dotconfig save
Keeping .env out of version control
Add .env to .gitignore — it is a generated file:
# Generated by dotconfig load — do not commit
.env
The source files in config/ are committed. Encrypted secrets files
(config/secrets/) are safe to commit because they are SOPS-encrypted.
Adding a new deployment
- Create the deployment directory and its public config:
mkdir -p config/{name} $EDITOR config/{name}/public.env # add public variables
- Load the new deployment to generate
.env:dotconfig load -d {name}
- Add any secrets to the secrets section in
.env, then save back:$EDITOR .env # add values under the secrets section dotconfig save # encrypts secrets via SOPS automatically
Adding a new developer
- Ask the developer to generate an age key pair:
age-keygen -o ~/.config/sops/age/keys.txt # Share the PUBLIC key (age1...) with the team
- Add their public key to
config/sops.yamland re-encrypt secrets:SOPS_CONFIG=config/sops.yaml sops updatekeys config/dev/secrets.env SOPS_CONFIG=config/sops.yaml sops updatekeys config/prod/secrets.env
- The developer creates their local overrides:
cp -r config/local/ericbusboom config/local/theirname $EDITOR config/local/theirname/public.env dotconfig load -d dev -l theirname
Design decisions
| Decision | Rationale |
|---|---|
Single .env file |
Tools (dotenv, Docker, IDEs) read one file — no cascade-compatibility issues. |
| Marked sections | Enable round-tripping between .env and config/ source files without extra metadata files. |
| Open deployment names | Not limited to dev/prod; supports test, ci, staging, or any custom name. |
| Last-write-wins ordering | Later sections override earlier when shell-sourced; local overrides deployment. |
| SOPS optional at load time | Developers without SOPS access can still load public config; secrets are skipped with a warning. |
| No shell variable expansion | Values are literal strings — no $VAR interpolation. Use a local override to change a value for a specific machine. |
| Local secrets are optional | config/local/{user}/secrets.env is supported but most developers won't need it. |
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 edotconfig-0.20260319.1.tar.gz.
File metadata
- Download URL: edotconfig-0.20260319.1.tar.gz
- Upload date:
- Size: 77.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b4b852afba420959bc3b2661ad6fef52723501914bb855de699e44e1e269d46
|
|
| MD5 |
4534a7ffda1fbe42ca454dc46212227d
|
|
| BLAKE2b-256 |
36be4bd9ebfdd395ffe92420661c69a66c2d2719f6e0361d581bf00fe422ab43
|
File details
Details for the file edotconfig-0.20260319.1-py3-none-any.whl.
File metadata
- Download URL: edotconfig-0.20260319.1-py3-none-any.whl
- Upload date:
- Size: 46.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14ec792f4e2bc1186649519ae45e98508819dcde8835b2f900b9aef2242be4e6
|
|
| MD5 |
a82bbeaffa00ef9c9e3868c212400ade
|
|
| BLAKE2b-256 |
4bcdcec806f5cf21addd995bb8a3861dd1e2a91045d94e708b48075419e5c722
|