Migrate from a .env file
You have a repository with a .env file that everyone copies around. This moves
it into prick, proves nothing was lost on the way, and gets the file out of the
repository for good.
Before you begin
- A project and environment to move into. If you have neither, do Onboard a new service first.
writeroradminon the target environment.
export PRK_PROJECT=apiexport PRK_ENV=production1. Look at the file first
grep -c '' .envThe parser is strict, so it is worth knowing what is in there before you upload. These forms all work:
# A commentDATABASE_URL=postgres://app:hunter2@db.example.com:5432/appSTRIPE_SECRET_KEY="sk_live_51H..."LITERAL_VALUE='no \escapes \here'export LEGACY_KEY=still-fineThese will be refused, and each is refused rather than guessed at:
| Line | Why |
|---|---|
KEY=one twice |
A duplicate key is an error, not last-one-wins |
KEY="unterminated |
Unterminated quote |
KEY="bad \q escape" |
Unknown escape |
KEY=`value` |
Backtick-quoted values are not accepted |
KEY=value # comment |
On an unquoted value, # reads two ways |
JUST_A_WORD |
No = |
1FOO=bar |
A key may not start with a digit |
FOO-BAR=baz |
Only A-Z, a-z, 0-9 and _ are allowed in a key |
A file it cannot parse unambiguously fails whole. Importing a file and
silently dropping the two lines the parser did not understand is how a deploy
loses DATABASE_URL.
Comments on unquoted values are the one to expect
The # row is the one most .env files trip over, and it is worth
understanding rather than just fixing. This line:
PASSWORD=hunter2 # 1says two different things depending on who reads it: the password is
hunter2 # 1, or the password is hunter2 and someone left a note. Both kinds
of file exist. A parser that picks one hands production a credential you never
wrote and reports success either way, so this one refuses the line and asks you
which you meant. Write whichever you actually have:
PASSWORD="hunter2 # 1" # the hash is part of the passwordPASSWORD="hunter2" # 1 # it is a commentOnly whitespace before the # is ambiguous. COLOR=#ffffff, TOKEN=ab#cd and
a # after a closing quote all parse as written, and every whole-line comment
is ignored as usual.
To see how many lines this affects before you start:
grep -nE "^[^#][^=]*=[^\"'].*[ \t]#" .env2. Dry-run the upload
prk secrets upload .env --dry-run3 added, 0 changed, 0 removed (dry run; nothing was written).Nothing was written. Read those numbers carefully — removed is the one that
bites.
If it refuses the file
error: Line 12: the double-quoted value of "STRIPE_SECRET_KEY" is never closed. help: Check for an unescaped double quote inside the value.Fix that line and run the dry run again. The message names the line and the key,
never the value — a rejected line of a .env file is by construction a line
containing a secret, and that message travels into an HTTP response and a log.
If you get an interpolation warning
warning: line 8: value contains a $VAR-like sequence and will be stored literally -- this parser performs no interpolation. (DATABASE_URL)This one matters. A .env file like:
POSTGRES_HOST=db.example.comDATABASE_URL=postgres://app@${POSTGRES_HOST}/appstores DATABASE_URL with the literal text ${POSTGRES_HOST} in it, because
this parser expands nothing. Whatever was expanding those variables before was
your shell or your framework, not the file. Expand them before uploading:
set -a && . ./.env && set +aThen write the expanded values out and upload that instead.
3. Upload
prk secrets upload .env3 added, 0 changed, 0 removed.upload replaces the environment
Keys the file does not name are deleted. That is what “upload this
environment” means, and it is why the dry run exists. If you are adding to an
environment that already has secrets in it, pass --merge:
prk secrets upload .env --merge4. Prove nothing was lost
Download it straight back and compare:
prk secrets download --format env --output /tmp/roundtrip.envWrote 3 secrets to /tmp/roundtrip.env.diff <(sort .env | grep -v '^#' | grep -v '^$') <(sort /tmp/roundtrip.env)Expect differences in quoting only — the export format quotes
unconditionally, so KEY=value comes back as KEY="value". If a value differs,
stop and work out why before deleting anything.
For an exact comparison that ignores quoting, use JSON:
prk secrets download --format json | jq -S .{ "DATABASE_URL": "postgres://app:hunter2@db.example.com:5432/app", "LEGACY_KEY": "still-fine", "STRIPE_SECRET_KEY": "sk_live_51H..."}Clean up the copy:
rm /tmp/roundtrip.env5. Switch the application over
Replace whatever loaded the file with prk run:
"start": "node server.js""start": "prk run -- node server.js"Or, if the process manager owns the environment, keep the file out of the repository at least:
prk secrets download --format env --output /run/app/.env--output creates the file with mode 0600.
6. Remove the file, and its history
rm .env.env.env.*!.env.exampleLeave a .env.example behind with the names and no values, so a new
contributor knows what the application needs:
prk secrets list --json | jq -r '.[].key + "="' > .env.exampleDATABASE_URL=LEGACY_KEY=STRIPE_SECRET_KEY=If the file was ever committed, the secrets are still leaked
Deleting a file does not remove it from git history — anyone with a clone still
has every value. Treat every secret that was in a committed .env as
compromised and rotate it. See
Respond to a leaked secret.
7. Do the other environments
prk secrets upload staging.env --env staging --dry-run3 added, 0 changed, 0 removed (dry run; nothing was written).prk secrets upload staging.env --env stagingMigrating from JSON instead
A .json extension is parsed as JSON rather than as a .env document:
prk secrets upload secrets.jsonThe file must be a flat object of string values:
{ "DATABASE_URL": "postgres://app@db.example.com/app", "STRIPE_SECRET_KEY": "sk_live_51H..."}That is also the shape prk secrets download --format json writes, so moving an
environment between servers is a download and an upload.
Next steps
prk secrets— every flag onuploadanddownload.- Using secrets — how the application reads them now.
- Script prk with
--json