Usage
envless syncPulls the latest variables for each environment in your .envless file, decrypts them locally, and writes them to .env.<slug> files. Idempotent: running sync twice in a row with no remote changes is a no-op.
envless pull is an alias of envless sync: identical behavior, same flags. Use whichever reads better in your scripts.
envless is the binary from @goenvless/cli, installed with npm i -g @goenvless/cli@latest. That package is a binary and nothing else, so there is nothing to import from it. The runtime your application imports is @goenvless/env, and it is a separate install.
Inline mode: no .envless required
The .envless file is a convenience, not a requirement. If it exists, envless sync reads the workspace, project, and environments from it. If it doesn't, pass them inline:
envless sync --workspace acme-co --project billing-api --env production
envless sync --workspace acme-co --project billing-api --env production,stagingInline flags also override the file when both are present, so you can sync a one-off environment without editing .envless:
# .envless is linked to development + staging, but sync production just this once
envless sync --env productionResolution order for each field is: flag → environment variable → .envless → error. The --product, --project and --env fields read ENVLESS_PRODUCT, ENVLESS_PROJECT and ENVLESS_ENV between the flag and the file. --workspace has no environment variable, so it comes from the flag or the file only. If a field is still missing after all sources, sync tells you which flag to pass (or to run envless link). Workspace and project accept either a slug or a uniqueId.
Interactive prerequisites
On a workstation, sync uses your device login and the cached workspace key. Sign in, link, and unlock once:
envless login
envless link
envless passphrase set
envless syncIf the workspace is locked, sync exits early and tells you to run envless passphrase set (or to set ENVLESS_KEY). The cached key lives in ~/.envless/config.json under workspaceKeys[<workspaceId>] as an exported JWK, so subsequent syncs reuse it. sync itself never prompts for a passphrase, it either finds a key or stops.
Non-interactive (CI / servers)
There's no browser or cached key in CI, so drive sync entirely from environment variables. Two credentials plus a target:
export ENVLESS_TOKEN=ev_sk_xxxxxxxxxxxx # machine key, dashboard → Keys
export ENVLESS_KEY=xxxxxxxxxxxx # workspace key, Settings → CI / Machine Access
envless sync --project billing-api --env production| Variable / flag | Role |
|---|---|
ENVLESS_TOKEN | ev_sk_ machine key. Authenticates the fetch and scopes the workspace, so --workspace isn't needed. |
ENVLESS_KEY | base64url raw workspace key. Decrypts locally. |
ENVLESS_PASSPHRASE | Alternative to ENVLESS_KEY; the key is derived from it locally, after one extra call that resolves the workspace the salt is built from. |
--product / ENVLESS_PRODUCT | Product slug (or a .envless file). Optional: only needed when the project slug exists in more than one product. |
--project / ENVLESS_PROJECT | Project slug (or a .envless file). Required. |
--env / ENVLESS_ENV | Environment slug(s), comma-separated (or a .envless file). Required. |
In machine mode the ev_sk_ token already scopes the workspace, so only --project and --env (or a .envless file) are needed to pick the target (no --workspace). Set ENVLESS_KEY or ENVLESS_PASSPHRASE for decryption. If both are set, ENVLESS_KEY wins and the passphrase is ignored.
Output files
One file per resolved environment, always named .env.<slug>. If the environments are ["development", "staging"], sync writes .env.development and .env.staging. Even when only one environment is selected, the suffix is always there (never plain .env). That keeps it unambiguous which env a file belongs to, and adding another env later won't rename anything that already exists.
Files are written with mode 0600. The header is two lines and a blank line, then the body:
# Generated by envless, do not edit by hand.
# Synced at: 2026-05-28T08:30:00.000Z
DATABASE_URL=postgres://localhost:5432/myapp
GREETING="hello there"
STRIPE_SECRET=sk_test_abc123Lines are sorted alphabetically by name. A value is quoted when it is empty, when it contains whitespace, ", ', =, #, $, a backtick or a backslash, or when it contains a control character. Inside the quotes, backslashes and double quotes are escaped, and newlines and carriage returns become \n and \r. A variable whose value is an empty string is written as NAME="".
Names are written with the casing they carry in Envless. Sync never upper-cases anything, so a variable stored as port is written as port. Because an environment cannot hold two names that differ only in case, no two lines in a generated file can collide that way either.
Two kinds of variable do not reach the file. One with no value at all is reported (⚠ Variable NAME has no value, skipping it.) and the rest are still written. One whose name holds a character a .env file cannot express (a space, =, ", ', #, a backtick, $, a backslash or a control character) is left out and the names are listed in a warning. The writer is deliberately more permissive than the create rule: a legacy name like API-KEY no longer passes ^[A-Za-z_][A-Za-z0-9_]*$ and cannot be created today, but a .env reader still handles it, so sync writes it rather than dropping it.
Gitignore check
.env.<slug> holds plaintext secrets, so before each write sync asks git whether the path is ignored (git check-ignore -q). If git answers that it is not, sync warns and writes the file anyway:
⚠ .env.development is not ignored by git. It holds plaintext secrets, so add it to .gitignore before you commit.Pass --require-gitignore to turn that warning into a refusal for the whole run:
envless sync --require-gitignoreUnder that flag the check moves to the front. Once the target environments are resolved, and before the first variable is fetched or decrypted, sync asks git about .env.<slug> for every target at once. If any one of them is not ignored, the run stops there. Nothing is fetched, nothing is decrypted, no file is written, not even for the environments git was happy about, and sync exits 1:
$ envless sync --require-gitignore
┌ envless sync
✔ Workspace Acme Co
✔ Project Billing API
✔ 2 environments to sync
✗ .env.development, .env.staging are not ignored by git, so nothing was written. Add them to .gitignore first.The message names every file that failed the check, in one line, and there is no summary after it because no environment was attempted. With a single offending environment the wording turns singular, ✗ .env.development is not ignored by git, so nothing was written. Add it to .gitignore first.
That is the flag for CI and for any repo where a committed .env would be a real incident. It is all or nothing on purpose: a partial sync would leave you guessing which environments landed. The check is best effort in one direction only. When git is missing, or the directory is not a repository, the answer is inconclusive rather than negative, and sync carries on and writes the file with no warning even under --require-gitignore. Only a definite "not ignored" from git trips either the warning or the refusal.
No-op detection
Before writing, sync compares the new variable body with the existing file (ignoring the timestamp header). If the body is identical, the file is left untouched: no mtime change, no dev-server reload.
Errors
| Situation | Behavior |
|---|---|
No .envless and no inline flags | Names the missing flag(s); suggests envless link |
| Workspace locked (interactive) | Tells you to run envless passphrase set, or set ENVLESS_KEY |
| Machine mode, no key set | Tells you to set ENVLESS_KEY or ENVLESS_PASSPHRASE |
| Decrypt fails on a variable | Names the variable, points at the passphrase / ENVLESS_KEY, skips that environment and carries on with the rest |
| Env slug not in the project (interactive) | Stops before anything is written, names the missing env |
.env.<slug> not ignored by git, with --require-gitignore | Refuses up front, before any fetch or decrypt. Names every target file git does not ignore, writes nothing for any environment, prints no summary, and exits 1 |
| A fetch or decrypt failed on an environment | The remaining environments still sync; the summary reports how many succeeded and sync exits 1 |
Going deeper
Two tools that pair with a synced file.