Undo, for production configuration
Publish an environment and every variable is snapshotted as ciphertext in one locked transaction. Roll it back and every variable touched gets an attributed history entry, written in the same transaction as the change.
Publishing an environment copies seven fields for every live variable into a numbered snapshot: name, value, type, secret flag, visibility, required flag and default value. The value is copied verbatim as ciphertext, which is why publishing never needs the workspace key and the server never learns anything by doing it. Labels are derived server-side from a count, so the first publish is v1, and a Postgres advisory lock keyed to the environment means two concurrent publishes cannot race to the same number. Rolling back replays a snapshot in one more transaction and tells you exactly how many variables it added, updated and removed.
The guarantees, precisely
Including the limits, because a history feature you have wrong assumptions about is worse than none.
- 1 transaction
- Both run inside a single Postgres transaction under an advisory lock keyed to the environment, so a publish cannot interleave with a rollback and neither can half-apply.
- 4 event types
- created, updated, deleted and restored. Each event denormalizes the actor name and email next to the user id, and the foreign key nulls on delete, so history keeps naming the person after their account is gone.
- 1000
- A hard cap. The publish that would be the 1001st is refused rather than silently dropping the oldest, so you find out at publish time instead of when you need the snapshot.
- 8192 chars
- A single-variable update stores the previous ciphertext in its event when it fits under that length, which is what powers "copy old value" in the history drawer. Bulk applies and rollbacks record no previous value.
- 3 permissions
- versions:read, versions:create and versions:rollback are checked server-side on every version controller. The built-in Viewer role carries only the read one.
- no delete
- There is no path anywhere that deletes a version row, so labels are never recycled while the environment exists and a published label stays a stable address.
Per publish and per rollback
Per-variable history
Versions per environment
Previous-value retention limit
Gating versions
Route, command or SDK method
Publishing, inspecting, rolling back
The same lifecycle from the CLI, the REST API and the SDK. Take a snapshot before anything risky, because rollback does not take one for you.
- 1
Publish before a risky change
The note is optional, trimmed and capped at 200 characters. Publish accepts several environments in one run, producing one snapshot and one event per environment, and it emails every other active member that a new version landed.
envless publish "before the Stripe migration" --env production envless version publish --note "before the migration" --env staging,production - 2
Look at what you captured
Listing supports free-text search across label and note plus latest, oldest and label sort orders, with server-side pagination. Revealing a version decrypts it locally, exactly like reading a live variable.
envless version list --env production envless version get v12 --env production --reveal - 3
Roll back in one transaction
A live variable counts as changed when any of six fields differs from the snapshot. Every variable the rollback touches gets a history event stamped with the version it came from, written inside the same transaction as the change.
envless version restore v11 --env production --yes - 4
Read one variable's story
Per-variable history is the surface you actually reach for during an incident: who changed this, when, and what was it before. It is queryable from the CLI, the API and the SDK.
import { envless } from '@goenvless/sdk' const events = await envless.variables.listAllHistory('api', 'production', 'DATABASE_URL') for (const event of events) { console.log(event.type, event.actor?.email, event.createdAt) } - 5
Ship the events somewhere permanent
Workspace events are delivered as signed webhooks, which today is how you build a durable, queryable record outside Envless. Delivery is at-least-once with a 30s, 2m, 10m, 30m backoff over five attempts, and only a 2xx inside ten seconds counts.
curl -X POST https://api.envless.cloud/webhooks \ -H "Authorization: Bearer $ENVLESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"audit-sink","url":"https://hooks.acme.inc/envless","eventTypes":["variable_version.created","variable_version.rolled_back"]}'
Read the details
Version mechanics, the event catalogue, and how to verify a delivery came from us.
- Versions Publishing, listing, inspecting and restoring, with every flag.
- Variable history Reading a single variable's change history and what each event carries.
- Webhooks overview Delivery semantics, retries, and the guarantees you can rely on.
- Verifying signatures Standard Webhooks signing, the tolerance window, and the raw-body rule.
- Version published event The exact payload shape emitted when someone publishes an environment.
- Rotating with version history Why version entries must be re-encrypted in the same run as live variables.
Versioning and history FAQ
Straight answers about how this works in practice.
No, only to a version someone explicitly published. Changes made between publishes are not recoverable as a set. Individually they often are, because a single-variable update stores its previous ciphertext in the event, but a bulk apply or a rollback records no previous value. Publish before anything you might want to undo, and treat publishing as cheap.
No, and this catches people out. Rollback replays the target version and writes no new version, so if you roll back and then want to get to where you just were, there has to have been a publish. Publish first, then roll back. The SDK documentation says the same thing at the call site for exactly this reason.
Not yet, and we would rather be precise than imply one. Every workspace action is recorded with the actor, the event type and a full payload, and each one is delivered to your webhook endpoint within seconds. What does not exist today is an endpoint or dashboard page that queries those rows back. The two surfaces you can query now are per-variable history and the version list; for a searchable workspace-wide record, point a webhook at a sink you control.
No. Nothing logs reads. The recorded events are created, updated, deleted and restored, which is to say writes. The nearest thing to read telemetry is a lastUsedAt timestamp on each API key, throttled to at most one write a minute, which tells you a key is in use but not what it fetched.
It is append-only in the sense that no route, command or SDK method exposes an update or delete for a version, and that is the honest way to put it. There is no hash chain, no signature over the rows and no write-once storage, and version ciphertext is deliberately rewritten in place when you rotate the workspace passphrase. Treat a version as a frozen set of variables rather than frozen bytes.
No, they are per environment, derived from that environment's version count. v13 in staging and v13 in production are unrelated snapshots. For anything that has to be globally unambiguous, such as pinning a build, use the version uniqueId rather than the label.
No, and this is deliberate. Duplicate copies products, projects, environments and variables including their ciphertext, but nothing in the duplication path touches versions. A duplicated environment starts with a clean history, which is usually what you want when the copy is a new staging branch rather than a clone of the past.
It starts fresh. A rollback soft-deletes variables missing from the snapshot and inserts new rows for names that are absent, and history is keyed to the variable row, so a restored variable begins a new history while the old events stay attached to the retired row. The version snapshots remain the continuous record across that boundary.
Explore the rest of Envless
Every capability, broken down the same way.
AI agents
Give a coding agent working secrets with no plaintext .env in the repository for it to read or commit.
TerminalThe CLI
24 commands, browser device login, local encryption, and JSON output on every list.
TypeScriptThe SDK
Zero-dependency typed client, full API coverage enforced by CI, with encryption and webhook helpers.
EncryptionEnd-to-end encryption
AES-256-GCM on your machine, a key the server never sees, and an API that structurally refuses plaintext.
TeamsTeam access control
44 permissions, custom roles, private products isolated to named members, and keys clamped to their creator.
EnvironmentsEnvironment variables
Typed, per-environment variables with server/client splitting, .env import and export, and a real diff.
Public feedsVirtual environments
Serve an environment as an encrypted JSON feed at a public URL that needs no SDK, CLI or API key.
SetupGetting set up
Install, sign in, bind the directory, unlock, run. What each step writes, and what it does not.
CloningOne-click duplicate
Clone an environment, project or whole product with every variable, in one transaction, without decrypting anything.
Ship secrets, not chaos.
Start free today and discover why developers trust Envless for end-to-end encrypted, versioned secrets across every environment.