Skip to main content

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 .env file.

Table of Contents


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:

  1. SOPS_AGE_KEY_FILE environment variable (path to an age private key file)
  2. SOPS_AGE_KEY environment variable (inline age private key)
  3. sops.yaml specified via --config flag or SOPS_CONFIG environment 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

  1. Create the deployment directory and its public config:
    mkdir -p config/{name}
    $EDITOR config/{name}/public.env      # add public variables
    
  2. Load the new deployment to generate .env:
    dotconfig load -d {name}
    
  3. 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

  1. 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
    
  2. Add their public key to config/sops.yaml and 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
    
  3. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

edotconfig-0.20260319.1.tar.gz (77.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

edotconfig-0.20260319.1-py3-none-any.whl (46.7 kB view details)

Uploaded Python 3

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

Hashes for edotconfig-0.20260319.1.tar.gz
Algorithm Hash digest
SHA256 8b4b852afba420959bc3b2661ad6fef52723501914bb855de699e44e1e269d46
MD5 4534a7ffda1fbe42ca454dc46212227d
BLAKE2b-256 36be4bd9ebfdd395ffe92420661c69a66c2d2719f6e0361d581bf00fe422ab43

See more details on using hashes here.

File details

Details for the file edotconfig-0.20260319.1-py3-none-any.whl.

File metadata

File hashes

Hashes for edotconfig-0.20260319.1-py3-none-any.whl
Algorithm Hash digest
SHA256 14ec792f4e2bc1186649519ae45e98508819dcde8835b2f900b9aef2242be4e6
MD5 a82bbeaffa00ef9c9e3868c212400ade
BLAKE2b-256 4bcdcec806f5cf21addd995bb8a3861dd1e2a91045d94e708b48075419e5c722

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page