Skip to content
Esc
↑↓navigate↵open⌘Jpreview
On this page

Move an existing .env into varsafe

Import a plaintext .env file into a varsafe environment with varsafe import, check that your app still starts, then delete the file and rotate anything it exposed.

CLI v7.7.0

You have a .env file with real values in it. One command moves them into varsafe. Then you check that your app starts with varsafe run, and delete the plaintext file.

Prerequisites

You need

The CLI, version 7.8.0 or later (varsafe update upgrades it), signed in with Full access on the approval page (see Getting started), and a role that can change secrets in the target environment. In a protected environment that means owner or admin.

Where it goes

The project and environment of your current context. varsafe status shows it, and varsafe use changes it.

Import it

See what will happen first. Nothing is written:

varsafe import .env --dry-run

Then import. At a terminal it shows the same plan and asks before writing:

varsafe import .env
Importing .env into my-api/development
  add      DATABASE_URL
  add      STRIPE_KEY
  add      SENTRY_DSN
  skip     node_env  line 5: not a key varsafe can store (A-Z first, then A-Z, 0-9 or _)
✔ Imported 3 secrets into development

What it does with your file:

  • It reads the file the way a .env loader does. Quotes are removed, export is dropped, a quoted value can span several lines (a private key, say), and # comments are ignored. An empty value such as SENTRY_DSN= is imported as empty, which is what your app saw before.
  • It never overwrites by default. A key the environment already has is listed as kept. To replace those values with the file’s, add --replace-existing. A secret composed from others is always kept.
  • It checks the whole file before writing anything. A key varsafe cannot store, or a value over 64 KB, is skipped and named by its line. A key set twice, or a line that is not KEY=value, stops the import: fix the file, then run it again. It never prints a value, or a line it could not read.
  • A protected environment asks you to type its slug. In a script, pass --yes once you have checked the target.

To import into another project or environment, name both: varsafe import .env -p my-api -e staging. A single -e takes the project from your user-level context, not from the repository (see Local development).

If an import stops partway, it lists what was imported, what the API refused (nothing in a refused request is written, for example when a secret changed after the plan was made), what may or may not have been written after a dropped connection, and what was never sent. Check with varsafe list, then run the same command again: it shows its plan first, and keys that were imported are now kept (with --replace-existing, they are written again with the same values).

The same flow, scripted

This script runs against a real varsafe server on every change to the docs. It imports, checks the values from inside a process without printing them, then replaces one on purpose:

# docs-test: local-stack
# Move a plaintext .env into varsafe with `varsafe import`, then check the result
# without printing a value.
set -euo pipefail

# Your project. Each command names it together with the environment: a flag on its
# own ignores the repository's .varsafe file and fills the other half from your
# user-level context.
PROJECT="${VARSAFE_PROJECT:-my-api}"

# A sample file for this example. Use your own .env instead.
printf '# Local settings\n' > example.env
printf 'PAYMENTS_API_KEY=vsafe_example_import_payments\n' >> example.env
printf 'QUOTED_TOKEN="vsafe_example_import_quoted"\n' >> example.env
printf 'export EXPORTED_TOKEN=vsafe_example_import_exported\n' >> example.env

# See the plan first: nothing is written.
varsafe import example.env -p "$PROJECT" -e development --dry-run

# Import. A key the environment already has is kept, never overwritten.
varsafe import example.env -p "$PROJECT" -e development --yes

# Check: a process started by `varsafe run` sees every key, with quotes and
# `export` handled as a .env loader does. `test` compares; nothing is printed.
varsafe run -p "$PROJECT" -e development -- sh -c '
  test "$PAYMENTS_API_KEY" = vsafe_example_import_payments &&
  test "$QUOTED_TOKEN" = vsafe_example_import_quoted &&
  test "$EXPORTED_TOKEN" = vsafe_example_import_exported'

# Replacing an existing key is a separate, explicit choice.
printf 'PAYMENTS_API_KEY=vsafe_example_import_payments_v2\n' > example.env
varsafe import example.env -p "$PROJECT" -e development --replace-existing --yes
test "$(varsafe get PAYMENTS_API_KEY -p "$PROJECT" -e development)" = vsafe_example_import_payments_v2

# Then remove the plaintext file and keep the next one out of git.
rm example.env
grep -qxF '.env' .gitignore 2>/dev/null || printf '.env\n' >> .gitignore

echo "OK:migrate-dotenv"

Or use the dashboard

  1. Open the project, select the environment, and click Add secrets.
  2. Under or import multiple, drop the .env file, click Upload file, or paste the contents.
  3. Check the preview: it lists every key it parsed, and the button names the count, for example Add 12 secrets.
  4. Click it. If some keys already exist, varsafe asks before replacing them (Replace and add).

The dashboard reads less of the .env format than varsafe import: one KEY=value per line, with one pair of surrounding quotes removed. It does not read multi-line values, inline comments or escape sequences. It also renames a key to fit (export API_KEY becomes EXPORT_API_KEY, apiKey becomes API_KEY), and silently drops a key it cannot fix. For a file with any of those, use varsafe import, or paste JSON ({"TLS_KEY": "line one\nline two"}), whose escapes are decoded. More on this panel: Dashboard: add secrets.

Check the result

List the keys in the environment. Values are not shown:

varsafe list

Then check that a process started by varsafe run sees a variable, without printing it. ${VAR:+set} prints set when the variable has a value:

varsafe run -- sh -c 'echo "DATABASE_URL is ${DATABASE_URL:+set}"'

Last, start your app the way you will from now on, for example varsafe run -- npm run dev, and use it.

Delete the plaintext file

Once the app works from varsafe, delete the file and keep the next one out of git:

rm .env
grep -qxF '.env' .gitignore || echo '.env' >> .gitignore

If a tool insists on reading a file, export an encrypted .env instead of keeping a plaintext one.

Rotate anything the file exposed

To check whether a .env was ever committed in a repository:

git log --all --oneline -- .env

Any output means the file is in history. Rotate first; rewriting history afterwards is optional cleanup, because existing clones keep the old commits.

Next steps

  • Local development: one default context per repository, and switching environments safely
  • Composition: build DATABASE_URL from parts instead of storing it twice
  • CI/CD: give your pipeline the same secrets with a token of its own