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.
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
.envloader does. Quotes are removed,exportis dropped, a quoted value can span several lines (a private key, say), and# commentsare ignored. An empty value such asSENTRY_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
--yesonce 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
- Open the project, select the environment, and click Add secrets.
- Under or import multiple, drop the
.envfile, click Upload file, or paste the contents. - Check the preview: it lists every key it parsed, and the button names the count, for example Add 12 secrets.
- 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 listThen 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_URLfrom parts instead of storing it twice - CI/CD: give your pipeline the same secrets with a token of its own