Skip to content

dotenv-fusion

Keep one readable configuration contract, resolve it for the current environment, and deliver it in the form your application or tools already understand.

dotenv-fusion logo

Start with an ordinary .env, then add imports, conditions, validation, and encrypted secret sources only where they help.

.env-fuse
ENV=${ENV:-development}

#@import config/${ENV}.env
#@def DATABASE_PASSWORD required=true source="age://store/application.env.age#DATABASE_PASSWORD"

DATABASE_URL=postgresql://application:${DATABASE_PASSWORD}@database/app

Choose how to deliver the resolved configuration:

dotenv-fusion exec -- application --serve

The application receives the variables directly. No intermediate .env file is created.

eval "$(dotenv-fusion load)"

Use this for an interactive development shell.

dotenv-fusion fuse -o .env

Use this when VS Code, Docker Compose, or another tool expects a flat file.

What do you want to do?

  • Start with dotenv-fusion


    Install the CLI and resolve your first configuration in a few minutes.

    Open the Quick Start →

  • Run an application


    Validate and inject configuration directly into a process.

    Run an Application →

  • Share a configuration contract


    Keep types, requirements, constraints, and secret declarations separate from developer-specific values.

    Define a Shared Schema →

  • Protect existing secrets


    Move selected values from an existing .env into an age-encrypted bundle.

    Encrypt Existing Secrets →

Why dotenv-fusion?

  • One source for several environments — use imports, conditions, and build-time variables without maintaining several complete files.
  • Explicit contracts — document types, defaults, required values, and validation next to each variable.
  • Safe runtime delivery — resolve encrypted sources immediately before process startup with exec.
  • Compatible outputs — generate ordinary dotenv files for tools that do not understand dotenv-fusion directives.
  • No runtime dependency — the core uses the Python standard library; the official age backend is loaded only when an age:// source is resolved.

Where next?