TILLSECRETS · HOW-TO MANUAL

TillSecrets, task by task.

GA

The other pages explain how TillSecrets works. This one is the operator’s manual: find the thing you want to do, and copy the exact steps. Every command below is real and current — CLI, SDK, or dashboard, whichever is the right tool for the job.

When you want to…Go to
Read a secret back into an app or a fileRead a secret
Change a secret’s value safelyRotate a value
Undo a bad changeRoll back
Give a service the least access it needsScope a token
Actually get a leased dynamic credentialRetrieve a lease
Add a Postgres / API-key / key-pair backendRegister a backend
Turn a key into a dynamic (leased) oneDeclare a dynamic key
Kill a leased credential now / see live onesRevoke & list leases
Mirror secrets to Cloudflare, Vercel, …Mirror to a platform
Retire a secretDelete a secret
See who read or leased whatRead the audit log
Respond to a leaked token or credentialRecover from a leak
01Read

Read a secret back

Most of the time an app reads its whole environment at boot. The quickstart covers the SDK path; from a shell or CI you can pull straight to a .env:

bash
# Pull the whole environment as dotenv — a read-scoped token is enough.
export TILLSECRETS_TOKEN=ts_…
tilldev secrets pull --env env_… > .env

# Read one value in an app (Node): pulled at boot, injected into process.env.
#   import { load } from '@tillstack/secrets-node'; await load()
# Or reveal a single value from the CLI (this is AUDITED — use sparingly):
tilldev secrets get <secret-id>

pull and load() take a read token. Revealing a single value with secrets get is recorded in the audit log — it’s for a human checking a value, not a hot path.

02Rotate

Change a value without downtime

Setting a key that already exists allocates a new version and makes it current — the previous value is never overwritten, so a rotation is always reversible.

bash
# Setting a key that already exists writes a NEW version; the old one is kept.
tilldev secrets set STRIPE_KEY --env env_… --value sk_live_new
# → Set STRIPE_KEY → v4

# Secrets are pulled at boot, so a running fleet keeps the old value until it
# re-pulls. Redeploy (or call pull() on a timer) to roll the new value out.
Pulls happen at boot
A running process keeps whatever it pulled at startup. To roll a new value out to a fleet, redeploy or re-run pull() — TillSecrets doesn’t push.
03Undo

Roll back a bad change

Every write is a version. If a value turns out wrong, promote the last-known-good one back to current — no re-typing the secret, and the rollback is itself audited:

bash
tilldev secrets versions <secret-id>            # find the good version
tilldev secrets rollback <secret-id> --version 2   # make v2 current again
04Access

Give a service least-privilege access

Apps authenticate with a ts_… service token. Two rules keep the blast radius small: scope read unless the service leases dynamic credentials, and pin the token to a single environment so it can never read another.

bash
# Read-only, pinned to ONE environment — what a service boots with.
tilldev secrets tokens create --project prj_… --scope read --env env_…

# read_write — needed ONLY to lease or revoke dynamic credentials (below).
tilldev secrets tokens create --project prj_… --scope read_write --env env_…

tilldev secrets tokens ls --project prj_…
tilldev secrets tokens revoke <token-id> --project prj_…
ScopeCan do
readPull static secrets. The default; give this to every normal consumer.
read_writeEverything read can, plus lease and revoke dynamic credentials. Grant only where leasing happens.
05Lease

Retrieve a leased dynamic credential

A dynamic secret has no stored value, so there is deliberately no “reveal” button anywhere in the dashboard. The credential exists only at the instant you lease it, and it is returned exactly once. You get it one of three ways.

From the CLI:

bash
export TILLSECRETS_TOKEN=ts_…          # a read_write token
tilldev secrets lease DATABASE_URL --ttl 900
# ↳ prints the lease id, the expiry, and every credential field — ONCE.

From an app, with the SDK:

lease.ts
import { createClient } from '@tillstack/secrets-node'

const secrets = createClient()                   // read_write TILLSECRETS_TOKEN in env
const { leaseId, credential, expiresAt } =
  await secrets.lease({ secretKey: 'DATABASE_URL', ttlSeconds: 900 })

// The credential FIELDS depend on the backend that minted it:
//   postgres → { username, password, host, port, database }
//   api_key  → { token, credential_ref }
//   keypair  → { algorithm, private_key, public_key_ssh, fingerprint, … }
// Assemble your DSN / write the key file, then use it until expiresAt.

Or over HTTP against the lease service directly — see Dynamic secrets → Leasing for the raw POST /v1/lease call.

Once, and never again
The credential is never persisted and never re-shown. The Leases ledger stores only a non-secret reference — the Postgres role name, an API key’s hash, or an SSH fingerprint. If you lose it, lease again for a fresh one; the old lease keeps working until its TTL or a revoke. That is why leasing needs a read_write token.
06Configure

Register a dynamic backend

Backends are registered in the dashboard under Secrets → Dynamic → Register backend (admin only). There is deliberately no CLI verb — a backend can hold an admin database password, which shouldn’t pass through shell history. Pick the type:

TypeUse it when
postgresYou own a database and want each app instance to lease its own time-boxed role. Needs an admin connection. See Backends.
api_keyYou want a strong random token per lease (webhook signing, per-tenant keys). Self-issued — no upstream. See Self-issued backends.
keypairYou need a fresh SSH / PKI key pair per lease (ed25519 or RSA). Self-issued; the private key is shown once. See Self-issued backends.

The self-issued types (api_key, keypair) need no connection — an empty {} config accepts every default. Add an optional revoke_webhook_url to make revocation enforceable at your consumer.

07Declare

Turn a key into a dynamic one

A backend can mint credentials, but an app leases them by a familiar env-var key. Declare that mapping in the dashboard (Secrets → Dynamic → Declare dynamic key), or with a session-authenticated call — the exact request is here. Once declared, the key shows up in a pull() under dynamic: [...], and lease() resolves it to that backend.

08Custody

Revoke a lease early & see live ones

Every lease self-expires at its TTL, and a background sweep reaps anything that slips past. To drop one immediately:

bash
# Hand a lease back before its TTL (needs a read_write token).
tilldev secrets lease revoke ls_…
# → Revoked lease ls_…   (Postgres roles are DROP-ed; self-issued creds are
#   marked revoked + your revoke webhook, if set, is called.)

The dashboard’s Secrets → Leases screen (admin) is the live custody ledger — every credential currently out, from which backend, with a countdown to expiry and a one-click Revoke. It shows the credential reference only, never the material.

09Distribute

Mirror secrets to a platform

If a runtime can’t pull at boot, mirror an environment into its native secret store. A sync target pushes the current values to cloudflare, vercel, railway, github, aws_ssm, dotenv, or a custom webhook.

bash
# Register a target once (provider config is envelope-encrypted).
tilldev secrets sync add --project prj_… --env env_… \
  --provider cloudflare --config '{"account_id":"…","api_token":"…"}'

tilldev secrets sync push <target-id> --project prj_…   # mirror now
tilldev secrets sync ls   --project prj_…               # status + last error
tilldev secrets sync pause <target-id> --project prj_…  # stop mirroring

See Config store → Sync targets for each provider’s config shape. Mirroring is a copy — the source of truth stays in TillSecrets.

10Retire

Delete a secret

Removing a key takes it out of future pulls; its version history is retained for the audit trail rather than hard-erased.

bash
tilldev secrets rm <secret-id>   # removes it from pulls; version history is kept

See Config store for versioning and soft-delete behaviour in full.

11Observe

See who read or leased what

Reveals, pulls, leases, revokes, token creation and use, and backend changes are all recorded. Read them under Secrets → Audit in the dashboard, or see Security → Audit log for the event shape and retention.

12Respond

Recover from a leak

Match the response to what leaked:

What leakedDo this
A service token (ts_…)tilldev secrets tokens revoke <id>, then mint a fresh one. Because tokens are env-pinned and scoped, the exposure was already bounded to one environment.
A static secret valueSet a new value (secrets set — a new version) and rotate it at the upstream. The old version stays in history but is no longer current.
A leased credentialIts short TTL already limits the window. secrets lease revoke ends it now: Postgres roles are dropped; self-issued credentials are marked revoked and your revoke_webhook_url (if set) is called. With no webhook, keep TTLs short and validate the credential_ref server-side.

New here? Start with the quickstart. Going deep on leasing? See Dynamic secrets. Want the trust model? Security. Back to the TillSecrets overview.