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, MySQL, Redis, AWS, custom, API-key or 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.

# Rotating a TillDev token? Kill the old one once nothing uses it:
tilldev secrets linked revoke FORGE_TOKEN --env prod
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), or with tilldev secrets backends create --type <type> --config-stdin. The CLI reads the config from stdin so an admin password stays out of 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.
mysqlThe same for MySQL or MariaDB: a user per lease with your grants, dropped at expiry. See MySQL.
redisA Redis or Valkey ACL user per lease with your rules. See Redis.
aws_iamAWS credentials per lease: an STS role session, or a throwaway IAM user and key. See AWS.
customAnything else: your HTTPS endpoint mints and revokes, TillSecrets signs each call and tracks the lease. See Custom.
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.
forge_tokenA CI job or agent needs to push or clone for a while: a TillForge token per lease. See TillForge tokens.

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), with tilldev secrets declare <KEY> --env <env> --backend <id>, 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_…   (the upstream user, role or key is dropped; self-issued
#   creds are marked revoked + your revoke webhook, if set, is called.)
# ! … not confirmed dropped yet   (the upstream was unreachable; retried until it is)

# Admins: every lease in the workspace, or only those still waiting on a drop.
tilldev secrets leases --pending
tilldev secrets leases revoke ls_…

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. A lease whose upstream drop wasn’t confirmed shows Drop pending until a retry gets through; the credential may work until then.

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).
# Pipe the config so the provider token stays out of shell history; or omit it to be prompted.
printf %s '{"account_id":"…","api_token":"…"}' | \
  tilldev secrets sync add --project web --env prod --provider cloudflare --config-stdin

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 STRIPE_KEY --env prod   # removes it from pulls; version history is kept
# Holds a TillDev token (tfp_, ts_, tp_…)? Revoke it at the source too:
tilldev secrets rm FORGE_TOKEN --env prod --revoke-linked

See Config store for versioning and soft-delete behaviour in full, and revoking TillDev tokens on delete or rotation.

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.

12Lock down

Restrict one secret

Give a secret tier 7 or above and it is restricted, even in an environment that isn’t. Project-wide tokens stop receiving it (it is listed in withheld), only the owner can reveal it, and TillForge CI gets it on protected branches only. You can also set the tier from the console, next to the secret.

bash
tilldev secrets update STRIPE_SECRET_KEY --env prod --sensitivity 9
# → STRIPE_SECRET_KEY tier → 9 (effective 9)

# The app that needs it gets its own token, pinned to prod and to that key.
tilldev secrets tokens create --project payments --env prod --keys STRIPE_SECRET_KEY --quiet

Lowering a tier below 7 needs the owner. Sensitivity tiers has every rule.

13Collect

Ask a person for a value

When a script or an agent needs a credential that someone else has, don’t ask them to paste it into a chat. Send a request: they fill it in their browser, and you get a handle.

bash
REF=$(tilldev secrets request DIGITALOCEAN_TOKEN --env prod \
  --note "Droplet read/write, for the job runner" --allow-host api.digitalocean.com)
# stderr shows a link and a code. Send both to the person who has the token.
echo "$REF"   # secretref://infra/prod/DIGITALOCEAN_TOKEN

Secret requests explains what the person sees and why the code matters. For someone outside the workspace, such as a vendor, add --external for a drop link: what they send waits for you to accept it.

14Delegate

Let a machine make one call

A machine that needs one privileged call — a droplet deleting itself when a job ends — should not hold the credential. Presign the call, and give the machine the tsc_ token.

bash
# Approve the call now…
tilldev secrets broker allow secretref://infra/prod/DIGITALOCEAN_TOKEN api.digitalocean.com \
  --methods GET,DELETE --path /v2/droplets
tilldev secrets presign DELETE "https://api.digitalocean.com/v2/droplets?tag_name=job-42" \
  --bearer secretref://infra/prod/DIGITALOCEAN_TOKEN --expires 6 --quiet > call.token

# …the machine runs it later, holding only call.token.
TILLSECRETS_CALL_TOKEN="$(cat call.token)" tilldev secrets call

tilldev secrets presigned ls              # uses left, expiry
tilldev secrets presigned revoke <id>

See Presigned calls for the limits and checks.

15Respond

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 presigned call token (tsc_…)tilldev secrets presigned revoke <id>. The token could only run the one call it was minted for, and only its remaining uses.
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.