DEV Community

Cover image for Stop editing the same value in every .env: one dotvault project for every environment, one Common layer
uehara
uehara

Posted on

Stop editing the same value in every .env: one dotvault project for every environment, one Common layer

This is part 3 of the series "dotvault, Start to Finish: One Command from Your Laptop to CI, Your Team, and Your AI Agents." dotvault will be released free of charge when this series ends.

In this part, a key disappears from development's key list, and yet the next development pull still writes it into .env. Nothing went wrong. That key now lives in Common, the layer dotvault merges into the pull of every environment, and it is stored there exactly once.

That is the point of this part. If you keep a separate .env for development, staging and production, every value that is the same everywhere is written once per environment, and changing it means editing it in every file. Miss one, and that environment runs on the old value. In dotvault, those separate files become the development, staging and production environments of one project, managed in one place in the console. A value shared by every environment goes into Common once, so you change it once. When one environment needs something different, a key of the same name in that environment wins over Common.

This is part 3 of 12, continuing with the demo-app project from part 2.

The short version

  • The separate .env files become environments of one project: development, staging and production. You switch between them on the Keys tab and manage every environment's values in one place. You can add your own environments on the Settings tab (this part adds preview).
  • A value that is the same in every environment goes into Common (_shared), once. Common is merged into every environment's pull, so you never edit the same value in two places. If an environment has a key of the same name, the environment's value wins.
  • Compare envs… shows the keys one environment has and the other lacks, and copies them in place. Promote to Common turns a key that already sits in your environments into a single Common key, using the value of the environment you selected. The same-named key leaves the standard environments and stays in Common alone. Environments you added yourself keep their own key.
  • On the CLI, the environment is chosen by -E, then DOTVAULT_ENV (or APP_ENV if that is unset), then defaultEnv in project-config.json.
  • A finished project is archived first, then deleted, in the console.

Before you read on

  • Where we start. Part 2 left demo-app with three keys in development and a successful dotvault pull. You can follow this part without having read it.
  • Versions and setup. Screens are from the production console at app.dotvault.io and CLI 0.15.7, in the same Node 20 Linux container as part 2. From 0.15.8 on, the .env marker line reads # BEGIN: dotvault (managed by dotvault). I checked that environment selection (-E, DOTVAULT_ENV, defaultEnv) behaves the same on 0.15.8.
  • Terminal screens are recomposed. The actual output is reproduced verbatim in a cleaner frame. The raw output is kept as is. Exit codes, recorded separately, are shown as an echo $? line.
  • Demo account. Project names, key names and values were created for this series. None of the values are real.
  • Vocabulary. "Secret" means any value that must not leak, such as an API key or a password. An "environment" is the unit that lets the same key hold a different value, such as development, staging or production. A "key" is the name half of a NAME=value line in .env.

1. One project holds every environment's .env

In dotvault, the .env you kept per environment becomes an environment inside one project. The demo-app project from part 2 has three: development, staging and production. They are dotvault's standard environments and cannot be removed. The Keys tab has an environment switch above the list; pick an environment and you see its keys. Every environment's values are read and edited from this one screen.

demo-app Keys tab (English UI): the environment switch shows development, staging, production, preview and Common (_shared), with preview selected; the list holds API_BASE_URL, DATABASE_URL and SMTP_PASSWORD; the Selected key panel is empty.

This screen already shows preview, which is added later in this part, and Common (_shared), where values shared by every environment go. We build up to it.

2. Compare two environments and copy what is missing

With every environment in one project, you can put two of them side by side. After part 2, the three keys exist only in development. Whether staging or production need the same keys is not visible until you compare. "⇄ Compare envs…" on the Keys tab opens a drawer with two environment selectors.

Compare environments drawer (English UI): production on the left, development on the right, "production ⇄ development · 0 in both", then "Only in development (2)" listing DATABASE_URL and SMTP_PASSWORD. A "Copy all to production →" button next to the heading and a "Copy to production" button on each row.

"Only in development" lists the keys the other side lacks. The "Copy to production" button on a row copies that one key; "Copy all to production" copies every key in the list. For this article I first copied all three to staging. Comparing staging with development afterwards shows a single line.

These two screens were taken after section 3 moved API_BASE_URL to Common. Keys in Common are not part of the comparison, so only two keys (DATABASE_URL and SMTP_PASSWORD) appear.

Compare environments drawer with staging and development selected (English UI): "staging ⇄ development · 2 in both" and "No differences — both hold the same keys."

What gets copied is the key name and the value at that moment. Changing the development value later does not propagate to staging. That is the point of environments, so it is intended. A key that should stay identical everywhere is not copied around; it goes into Common, next.

3. Keys that are the same everywhere go into Common, once

In this demo, API_BASE_URL holds the same value in every environment. In a real project, this is the place for keys like a third-party service's public key or the address your app sends email from. Writing it into each environment means changing it in each environment. Common (shown as Common (_shared) in the console, _shared on the CLI) is the layer for that. A key placed in Common is written out with every environment's pull. There is one value, so a change is one edit.

A key already sitting in an environment moves into Common with "⬆ Promote to Common (_shared)". Select the key and press it; a confirmation appears.

Promote to Common (_shared) confirmation (English UI): "Promote API_BASE_URL to Common (_shared) using development's value.", a green note "development's value becomes the Common value — it underlies every env from now on.", a red note "The key is deleted from staging. Those envs' current values are lost.", then "To keep an env-specific value later, re-add an override in that env (env wins over Common)." Buttons: Cancel and "Promote & delete from other envs".

As the dialog says, development's value becomes the Common value. The same-named key leaves the three standard environments (development, staging, production) and lives in Common alone. Keys in environments you added yourself stay, and they still take priority over Common. After confirming, development and Common look like this.

development after the promotion (English UI): two rows, DATABASE_URL and SMTP_PASSWORD; API_BASE_URL is gone.

Common (_shared) tab after the promotion (English UI): a single row, API_BASE_URL, updated at the time of the promotion.

That is the key that vanished from development's list at the top of this article. The pulled .env still contains it, now as the Common value (checked in section 5).

When one environment needs a different value, add a key of the same name in that environment. The environment's value takes precedence over Common. Section 5 checks that too, with a different value in preview.

Common gathers the values shared by the environments of one project. Values shared across projects come in part 6, with Global Keys.

4. Add an environment of your own

When the standard three are not enough, add another environment to the same project; it sits next to the others on the same screens. Add it under Environments on the Settings tab. Names are lowercase letters, digits and hyphens, starting with a letter, at most 32 characters. "Copy keys from an existing env" copies that environment's keys into the new one right away. I added preview, copied from development.

Settings tab, Environments section (English UI): a five-row table: development, staging, production (Standard), preview (with Rename and Delete buttons) and _shared (Cannot be deleted or renamed, Common). Below it the name field (e.g. preview, qa-2), the Add environment button and the Copy keys from an existing env select; further down the Danger zone with Archive this project.

Environments you added get Rename and Delete. The standard three and _shared do not. Rename opens a dialog for the new name and notes that every key and permission referencing the old name is rewritten.

Rename "preview"… dialog (English UI): "Every key and permission that references this env name is rewritten to the new name.", a New name field (e.g. preview-2), Cancel and Rename.

An environment that still holds keys or permissions cannot be deleted. Pressing Delete on preview with keys in it, and confirming, shows "This environment still has keys or permissions. Remove those first." and nothing is removed. To delete it, first remove its keys and any permissions granted on that environment.

Environments section after confirming Delete on preview (English UI): a red banner "This environment still has keys or permissions. Remove those first." above the table; the preview row is still there.

After this I added API_BASE_URL to preview with a different value (https://preview.example.com/v1), to see which wins, Common or the environment.

5. Picking the environment on the CLI

The values kept in one place in the console come out on the terminal by naming the environment. dotvault list demo-app prints the key count per environment, including the preview you added.

Terminal (recomposed):  raw `dotvault list demo-app` endraw  prints a table with env, file, keys and last_modified: development 3, preview 3, production 1, staging 3, all on .env.

production shows 1 although nothing was put there. That is Common being counted: API_BASE_URL in Common is merged into every environment.

-E (--env) selects the environment for a pull. Without it, and without the environment variables below, defaultEnv from project-config.json applies (development, as written by init in part 2).

dotvault pull
dotvault pull -E preview
dotvault pull -E production
Enter fullscreen mode Exit fullscreen mode

Terminal (recomposed):  raw `dotvault pull` endraw ,  raw `dotvault pull -E preview` endraw  and  raw `dotvault pull -E production` endraw , the first two each followed by  raw `cat .env` endraw . The development pull writes API_BASE_URL=https://api.example.com/v1; the preview pull writes https://preview.example.com/v1 with the other two keys unchanged; the production pull reports "✓ Wrote 1 keys … env=production".

Three things to notice.

  1. The development pull contains API_BASE_URL. It left development's list, but it comes back as the Common value. One value in Common reaches every environment's .env.
  2. The preview pull contains preview's API_BASE_URL. A same-named key in the environment beats Common.
  3. The written .env does not record which environment it came from. The result line always shows env=…. Before starting work, pull again for the environment you need.

DOTVAULT_ENV selects the environment too, for cases like CI or npm scripts where you want to switch without editing the command. When both are present, -E wins. If DOTVAULT_ENV is unset, the CLI also reads APP_ENV as the environment. So if your app already uses APP_ENV for its own settings, a plain pull with neither -E nor DOTVAULT_ENV gets that environment, not defaultEnv. DOTVAULT_ENV is the reliable way to pick the dotvault environment.

Terminal (recomposed):  raw `DOTVAULT_ENV=staging dotvault pull` endraw  writes env=staging;  raw `DOTVAULT_ENV=staging dotvault pull -E preview` endraw  writes env=preview; a second  raw `dotvault init` endraw  ends with "✖ VALIDATION_ERROR: project-config.json already exists. Use --force to overwrite." and exit code 6.

So the order is -E, then DOTVAULT_ENV (or APP_ENV), then defaultEnv. defaultEnv applies only when -E and both variables are absent. The same screen ends with a second dotvault init: in a folder that already has project-config.json it stops instead of overwriting, and --force is the way to start over.

How Written as Fits
Command flag dotvault pull -E staging A quick look at another environment
Environment variable DOTVAULT_ENV=staging dotvault pull CI and start scripts, switching without changing the command
Config file defaultEnv in project-config.json The folder's everyday environment; init writes development

defaultEnv takes one of development, staging or production. Pick an environment you added yourself, such as preview, with -E (or DOTVAULT_ENV) each time.

6. Putting a finished project away

A project that holds all your environments is also put away as a whole. The Danger zone sits at the bottom of the Settings tab. Archive this project asks for confirmation and then removes the project from the console's list. Archiving is reversible. Deleting is only possible after archiving.

Set the list filter to "Archived only" and the archived project appears with Unarchive and Delete… buttons.

Projects list with Archived only selected (English UI): the demo-app row marked archived, with Unarchive and Delete… buttons.

Delete… opens a dialog that asks you to type the project name. Delete permanently stays disabled until the name matches. What goes is not only the current values but the whole version history covered in part 4.

Delete "scratch-app" permanently dialog (English UI): "This permanently deletes the project and ALL its secrets — current values and full version history. This cannot be undone.", the Type "scratch-app" to confirm: field with scratch-app typed, Cancel and Delete permanently.

This screen was taken on a throwaway project, scratch-app, so that demo-app survives. After Delete permanently, the project is gone even under "All (incl. archived)". demo-app was unarchived and continues into part 4.

Archiving and deleting a project are done in the console. On the CLI, dotvault project offers create and list.

Wrapping up

The .env you used to keep per environment becomes an environment of one dotvault project, managed in one place in the console. A value every environment shares goes into Common once and reaches every pull, so you never edit the same value in two places.

Next

Part 4: "Every value keeps every version: writing, pulling and rolling back keys." Keys from both the console and the CLI, reading values out, and restoring an earlier version from the version list, one screen at a time.


dotvault stores the secrets in your .env and config files server-side and hands them to any authorised developer, CI job or AI agent with a single command (https://dotvault.io). It will be released free of charge when this series ends, and the steps in these articles are the steps you will use on day one.

If you find this useful, please like and follow.

Our products are listed at https://www.eln.ne.jp/products


EarthLink Network builds its own products to run the whole company on AI — each one born from a need along the way. See the full index of what we build:

→ The 18 products EarthLink Network builds in-house

For more about the company and each product, visit www.eln.ne.jp.

Top comments (0)