Undo, for production configuration

Publish snapshots every variable as ciphertext in one transaction. Every rollback leaves an attributed history entry.

The guarantees, precisely

Publishing copies every live variable into a numbered snapshot as ciphertext, so it never needs the workspace key. Rolling back replays a snapshot in one transaction.

Full definition

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 each publish only lands if the environment is still at the revision it read, so 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.

  • Per publish and per rollback

    1 transaction

    Both run as one atomic database batch that first checks the environment has not changed since it was read, so a publish cannot interleave with a rollback and neither can half-apply.

  • Per-variable history

    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.

  • Versions per environment

    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.

  • Previous-value retention limit

    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.

  • Gating versions

    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. The plan gates on top: Free keeps the latest published version only, and history plus rollback need Dev or above.

  • Route, command or SDK method

    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.

Publishing, inspecting, rolling back

  1. 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. 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. On Free the list holds only the latest version; Dev and above see the whole history.

    envless version list --env production
    envless version get v12 --env production --reveal
  3. 3

    Roll back in one transaction

    Rollback is a Dev and above feature. 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. 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. 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"]}'

Versioning and history FAQ

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.

Publishing works on every plan and no version is ever deleted, but Free keeps only the latest published version in view: listing returns the latest snapshot and reports how many are hidden, reading an earlier version answers with a plan.upgrade_required error, rollback is refused with the same error, and per-variable history returns the latest entry. Dev, Team and Enterprise get the full history and rollback, and everything published while on Free becomes visible the moment you upgrade.

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.