Skip to content
dot
Esc
↑↓navigate↵open⌘Jpreview
On this page

Secrets

Project secrets in 1Password, attached to a project by reference and never written to disk.

A project’s secrets (API keys, database URLs…) live in 1Password, and Varlock hands them to the project when it runs. No secret is written to a .env file or exported in the shell.

dot apply packages    # installs Varlock

Where they live

Project secrets go in a 1Password vault kept for development, apart from your logins. dot.toml names it:

[secrets]
vault = "Dev"

A project never stores a value: its .env.schema holds a reference, and Varlock resolves it when the project runs. Which secrets a project needs, and with what permissions, is the project’s own documentation.

Add a secret to a project

1. Create it with the least it needs

Give each secret only what its job requires, and one secret per job, so revoking one breaks nothing else. A job that only reads gets a read-only token; a test environment gets a test key. Creating it usually happens on the provider’s website; each provider’s guide has the exact steps (GitHub token).

2. Store it

dot secret add myapp-payments     # name it <project>-<use>

It asks for the value without showing it (or reads it from a pipe: pbpaste | dot secret add myapp-payments), stores it in the vault as an API Credential tagged dotfiles, and prints its reference, op://Dev/myapp-payments/credential. The value never becomes a command argument or a file, so it’s not in your shell history or the process list. It refuses a name that already exists.

dot secret list                   # names, dates and references; never values
dot secret update myapp-payments  # a new value, same reference

3. Attach it to the project

A project commits a .env.schema: which variables exist, their types, and where each value comes from. varlock init creates it, and its header loads the 1Password plugin. Then, from the project’s folder:

dot secret attach myapp-payments PAYMENTS_KEY

This adds the variable as a reference, never a value:

# @plugin(@varlock/1password-plugin)
# @initOp(allowAppAuth=forEnv(dev))
# ---
APP_URL=http://localhost:3000

# @sensitive
PAYMENTS_KEY=op(op://Dev/myapp-payments/credential)

dot secret attach refuses a variable the schema already has, and a schema without the plugin.

4. Run with it

varlock explain PAYMENTS_KEY  # how one variable resolves, without its value
varlock load                  # validate the schema and show what resolves
varlock run -- pnpm dev       # run a command with the values injected

A Next.js project uses Varlock’s integration instead (@varlock/nextjs-integration), so next dev and next build load the schema themselves. Local overrides (.env.local) belong in each project’s .gitignore and never hold a secret: that’s what the schema is for.

At runtime

  • Only that process gets the secrets. varlock run resolves the values (1Password asks for Touch ID) and injects them into the command it starts; your shell never has them.
  • Sensitive values are redacted in output that’s piped, such as logs.
  • Cache: resolved values can be cached in ~/.config/varlock/cache/, encrypted with a Secure Enclave key that needs Touch ID.

Telemetry

Varlock sends anonymous usage analytics unless they’re turned off. Here that’s a personal value:

[varlock]
telemetry = "disable"    # or "enable"

DO_NOT_TRACK=1 also turns it off for a single run.

Where each secret lives

Secret Lives in Reaches its user through
App secrets (API keys, DB URLs) 1Password, development vault Varlock, per process
GitHub token for gh 1Password the gh shell plugin, per command
Vercel token for vercel 1Password the vercel shell plugin, per command
A project’s Vercel OIDC token nowhere: issued per run, 12 hours Varlock, through the vercel plugin (Vercel)
SSH key 1Password the SSH agent
dot.toml backup 1Password dot conf restore

Further reading

Was this page helpful?