Why turn YAML into environment variables
Twelve-factor apps read configuration from the environment, but configuration is often designed as nested YAML: application.yaml in Spring, appsettings exported from .NET, Helm values or a local config.yml. Converting lets you:
- Seed a
.envfile for Docker Compose or a local dev server from the real config. - Fill a CI/CD secret store or a Kubernetes
env:block without retyping keys. - Check which variable name a nested setting will be read from.
How keys are built
The YAML is walked from the root, and every leaf value becomes one line. The path to the value becomes the variable name:
- Each key along the path has runs of characters other than letters and digits replaced by
_(feature-flagsbecomesFEATURE_FLAGS), with leading and trailing underscores trimmed. - The parts are joined with the Nesting separator and upper-cased:
database.pool.maxbecomesDATABASE_POOL_MAX. - A name that would start with a digit gets a leading underscore (
2fabecomes_2FA).
Variable prefix is prepended to every name, upper-cased and followed by the separator, so a prefix of app produces APP_DATABASE_HOST. If the prefix already ends in _, no extra separator is added.
Nesting separator offers Single underscore (_, the default) or Double underscore (__). The double form is what .NET configuration and pydantic-settings use to rebuild nesting, and it keeps db_host (one key) distinct from db.host (two levels).
How values are written
- Values made only of letters, digits and
_ . / : @ % + , = -are written bare:PORT=5432,URL=https://example.com. - Anything else, such as text with spaces, is wrapped in single quotes, which every dotenv implementation treats literally.
- Values containing a single quote or a line break use double quotes with
\n,\"and\\escapes. nulland empty mappings become an empty assignment (KEY=).- A list of scalars becomes one comma-separated value:
features: [payments, refunds]givesFEATURES=payments,refunds. - A list that contains mappings or other lists is expanded with index numbers:
USERS_0_NAME,USERS_1_NAME.
YAML is parsed as version 1.2, so on and yes reach the .env file as the literal text you wrote.
What is lost
Flattening is one-way. The structure survives only in the names, comments are dropped, and a list joined with commas cannot be told apart from a string that contained commas. When two paths produce the same name — db_host and db.host with the single separator — a warning names both, and the later value wins.
Remember that docker run --env-file reads quotes literally, unlike Docker Compose and most dotenv libraries; check quoted values if you use it.
Examples
Service config with a prefix
Every variable starts with CHECKOUT_, the feature list becomes one comma-separated value and the message with a space is single-quoted.
database:
host: db.internal
port: 5432
pool: { min: 2, max: 10 }
features:
- payments
- refunds
log_message: "Service started"
CHECKOUT_DATABASE_HOST=db.internal
CHECKOUT_DATABASE_PORT=5432
CHECKOUT_DATABASE_POOL_MIN=2
CHECKOUT_DATABASE_POOL_MAX=10
CHECKOUT_FEATURES=payments,refunds
CHECKOUT_LOG_MESSAGE='Service started'
Double underscore for .NET
Levels are joined with __, the shape .NET reads back into nested configuration, and the dot inside the logger name becomes a single underscore.
Logging:
LogLevel:
Default: Information
Microsoft.AspNetCore: Warning
ConnectionStrings:
Main: "Server=sql01;Database=shop;Trusted_Connection=True"
LOGGING__LOGLEVEL__DEFAULT=Information
LOGGING__LOGLEVEL__MICROSOFT_ASPNETCORE=Warning
CONNECTIONSTRINGS__MAIN='Server=sql01;Database=shop;Trusted_Connection=True'
List of mappings
Lists that hold mappings are expanded with an index in each name, such as UPSTREAMS_0_URL.
upstreams:
- name: api
url: http://api:8080
- name: auth
url: http://auth:9000
UPSTREAMS_0_NAME=api
UPSTREAMS_0_URL=http://api:8080
UPSTREAMS_1_NAME=auth
UPSTREAMS_1_URL=http://auth:9000
Common errors and how to fix them
| Error | Cause | Fix |
|---|---|---|
A .env file needs keys: the YAML must be a mapping | The YAML is a single scalar value, so there are no keys to turn into variable names. | Convert a mapping such as key: value, or put the value under a key. |
"db_host" and "db.host" both become DB_HOST; the later value wins | Two different paths flatten to the same variable name. | Switch the Nesting separator to Double underscore, or rename one of the keys. |
Tabs are not allowed for indentation in YAMLExplained | The YAML input uses a tab for indentation and cannot be parsed. | Replace tabs with spaces before converting. |
Frequently asked questions
Which separator should I choose?
Use a single underscore for conventional SCREAMING_SNAKE names. Choose the double underscore when the reading framework, such as ASP.NET Core or pydantic-settings, rebuilds nesting from __.
How are lists converted?
Lists of plain values become one comma-separated value. Lists containing mappings get one variable per field, with the item index in the name.
Why do some values have quotes?
Values with spaces or shell-sensitive characters are quoted so dotenv parsers read them exactly. Single quotes are used where possible because they are never interpreted.
Can I convert the .env file back to YAML?
Not reliably, because flattening loses the distinction between nesting and underscores in names. Keep the YAML as the source and regenerate the .env when it changes.
Are my secrets uploaded?
No. The YAML is converted inside this browser tab, which is why the page is safe to use with real credentials.