capy run in any workflow step that needs decrypted secrets - builds, tests, deploys, anything that reads process.env.
Unlike a runtime platform, GitHub Actions is a CI vehicle: the two secrets live in your repo’s Actions secret store, and each workflow step wraps its command with capy run. capy deploy has a built-in connector that pushes those secrets for you over the gh CLI - no copy-paste.
Set it up with capy deploy
The connector uses the GitHub CLI you already have authenticated locally - Capy never sees a token. Make sure gh is installed and logged in first:
- Pick GitHub Actions in the platform picker.
- Choose Push SECRETS_BLOB + PROJECT_KEY to GitHub secrets via gh.
- Pick a scope:
- Repository secrets - every workflow and every environment can read them.
- Environment secrets - scoped to one GitHub Actions environment (e.g.
production). Pick an existing environment or create one on the spot.
- Capy mints a fresh deploy token, runs
gh secret setforSECRETS_BLOBandPROJECT_KEYin the scope you chose, and prints the workflow patch to paste plus the deploy id (for revoking later).
gh repo view), so run it from inside the checkout you’re deploying.
The Actions secrets are named
SECRETS_BLOB and PROJECT_KEY - no CAPY_ prefix. If you set them up under different names in the past, update the env: block in your workflow to match, or re-run capy deploy to overwrite them.Workflow
The connector prints a short patch to paste: anInstall Capy CLI step pinned to the CLI version you have installed, plus your deploy step wrapped in capy run -- with a SECRETS_BLOB / PROJECT_KEY env: block. A complete workflow built around it looks like this:
env: block inherits SECRETS_BLOB and PROJECT_KEY, so you don’t have to repeat them per step.
Per-environment secrets
When you pick Environment secrets scope, the secrets land on one GitHub Actions environment. Your deploy job must pin to that environment for${{ secrets.* }} to resolve - the connector reminds you of this in its output:
capy deploy snapshots the .env in your working directory, so two runs from the same checkout push the same values to both environments. To give production and staging different secrets, check out the matching secret branch first, then deploy:
Non-interactive setup
Every connector prompt has a flag, so you can script the whole setup. You still need a signed-in Capy session on the machine that runs it -capy deploy opens a browser to sign in when it has no valid session - plus an authenticated gh:
capy deploy --help also lists --dry-run, --force, and --edit. Those belong to the adapter-based connectors (Cloudflare, Vercel, AWS SSM); the GitHub Actions connector ignores them, so --dry-run will not stop it from pushing real secrets.
Manual setup (without gh)
If you’d rather not use the connector - or gh isn’t available - choose the token path and set the secrets yourself:
127.0.0.1 and opens it in your browser. Copy SECRETS_BLOB and PROJECT_KEY from there and press Ctrl+C to close it (it also shuts itself down after five minutes). If the local server can’t start, Capy falls back to printing both values in the terminal.
Then add them under Settings → Secrets and variables → Actions, or:
One decrypt per step
Eachcapy run invocation decrypts on its own: it parses SECRETS_BLOB, fetches its half of the key from the Capy service, and decrypts in memory. There is no cache between steps, so every step that needs secrets gets its own capy run. Nothing decrypted is written to disk, so you can’t decrypt once and hand the values to a later step through a file or an artifact.
The one file capy run does write in deployed mode is .capy/next-env.js, and it carries no values - it maps each decrypted variable name to a process.env lookup so next build can inline them. It is only useful inside the same capy run process that produced it:
Pull requests from forks
By default, GitHub Actions doesn’t pass secrets to workflows triggered by PRs from forks - a security baseline. Forked-PR builds can’t decrypt with Capy. Usually fine: run CI without decrypted secrets, use mock values, or gate deploy jobs ongithub.event.pull_request.head.repo.full_name == github.repository.
Revocation
When the connector finishes it prints a shortened deploy id and the exact revoke command for it. TheSECRETS_BLOB + PROJECT_KEY stored in GitHub are long-lived, so revoke the token server-side to cut them off: