Skip to content

Injecting secrets into CI

The usual way to get a secret into CI is to paste it into your provider’s secret store. Then it lives in two places, rotating it means remembering both, and nothing records which pipeline read what.

reoclo run removes the copy. You store one credential in your CI provider (an automation key that can read nothing by itself) and grant that key read access to a project. A machine user token works the same way and is the better fit for an agent that needs more than secrets. At run time the credential resolves the values and injects them into your command as environment variables. Nothing is written to disk, nothing is printed, and every read is attributed to a workflow run and a commit.

This works the same on GitHub Actions, Gitea Actions, GitLab CI, Woodpecker, and your laptop.

You need a secret project with something in it (see the quickstart) and the Admin role to grant access to a key.

  1. Create an automation key for this pipeline

    In the dashboard, go to API Keys, switch to the Automation Keys tab, and create a key. It begins with rca_. Copy it now. It is shown once.

    Give it the secret_session:open operation and no servers. That one operation lets the key open a read-only secret session. The grant you add next decides which secret projects it reads. A key that can do nothing except read one secret project is exactly what belongs in CI, because a leaked key cannot deploy anything.

    Create one key per pipeline rather than sharing one. Revoking then costs you one pipeline instead of all of them.

  2. Grant the key read access to your project

    Open the project, go to the Access tab, and add a grant for the Automation key you just created, with read.

    This is the step that matters. Without it the key resolves nothing. As you will see in step 5, it fails loudly rather than quietly handing your command an empty environment.

    Repeat for each project this pipeline needs. Grants are per project. There is no global read.

  3. Store the key in your CI provider

    Add the rca_ key as a secret named REOCLO_AUTOMATION_KEY. On GitHub or Gitea that is Settings → Secrets and variables → Actions.

    This is the only Reoclo credential your CI provider ever holds. The actual secrets stay in Reoclo.

  4. Install the CLI and wrap your command

    reoclo run resolves every project the key can read and injects the values into whatever follows --:

    - name: Run migrations with injected secrets
    env:
    REOCLO_AUTOMATION_KEY: ${{ secrets.REOCLO_AUTOMATION_KEY }}
    run: |
    curl -sSL https://get.reoclo.com/cli | bash
    reoclo run -- ./migrate.sh

    Inside migrate.sh, every key in the granted project is just an environment variable. No SDK, no fetch, no parsing.

    Gitea’s runner is GitHub-Actions-compatible, so that step works there unchanged. For other systems, name your CI secret REOCLO_AUTOMATION_KEY, install the CLI, and run the same command:

    Terminal window
    curl -sSL https://get.reoclo.com/cli | bash
    reoclo run -- ./migrate.sh

    To pin the CLI instead of tracking latest, download a tagged install.sh from the releases and verify its checksum before running it.

  5. Confirm it resolved

    Check that a key arrived without printing it:

    Terminal window
    reoclo run -- sh -c '[ -n "$DATABASE_URL" ] && echo resolved'

    If the grant is missing, this does not print resolved and it does not run your command. It fails. That is the whole design: reoclo run fails closed, so a missing grant surfaces as a red pipeline, never as an app that booted with an empty DATABASE_URL.

With no flags, everything the key can read is injected, so adding a secret to a granted project needs no pipeline change.

Pass -p to restrict it, repeating the flag for more than one project:

Terminal window
reoclo run -p payments-production -- ./migrate.sh
reoclo run -p payments-production -p shared-infra -- ./deploy.sh

Naming a project the key cannot read fails without running your command. A project that exists but is not granted and one that does not exist report the same message, so a key cannot be used to enumerate your projects.

If you already keep a .env template for 1Password’s op inject, point it at Reoclo instead. run --env-file reads a template whose values are op:// references, resolves each one from the Secrets Manager, and injects the results:

Terminal window
# app.env.tpl
DATABASE_URL=op://payments-production/app/DATABASE_URL
STRIPE_API_KEY=op://payments-production/app/STRIPE_API_KEY
Terminal window
reoclo run --env-file app.env.tpl -- ./migrate.sh

Each reference is op://<project>/<item>/<field>. Reoclo maps it to the secret <field> in the project <project> and ignores <item>. The values come from Reoclo, not from 1Password, so the op command is not required. Because the template names its own projects, --env-file cannot combine with -p.

For a step that needs a real .env file on disk, reoclo secrets inject writes the rendered file instead of running a command:

Terminal window
reoclo secrets inject -i app.env.tpl -o .env

Prefer run --env-file where you can, because it keeps the values out of every file.

This is the one thing to internalize.

Your CI provider masks strings you registered with it. Values injected by reoclo run never passed through your CI provider, so it has no idea they are secret. If your command prints one, it lands in the log in the clear, and CI logs are often readable by more people than the secret is.

If you need to prove a value arrived, compare it instead of printing it:

Terminal window
reoclo run -- sh -c 'printf "%s" "$API_KEY" | sha256sum | cut -c1-8'

Each reoclo run opens a resolve session that expires after 15 minutes, and records the session plus the keys it resolved (names only, never values).

On GitHub and Gitea the workflow run id and commit SHA are picked up automatically, so every read is attributable with no extra wiring. Elsewhere, pass the commit yourself:

Terminal window
reoclo run --commit "$CI_COMMIT_SHA" -- ./release.sh

That gives you the question most secret stores cannot answer: which pipeline run, on which commit, read this key?

Rotate the secret in Reoclo. The next pipeline run picks it up. There is no CI change and no second copy to remember, because your CI provider never had the value.

Rotating the automation key does need a CI change: replace REOCLO_AUTOMATION_KEY and revoke the old key. Per-pipeline keys keep that blast radius to one pipeline.

SymptomCause
Exit 6, “no accessible secret projects”The key has no grants at all. Add one on the project’s Access tab.
Exit 6, “no accessible project matched”The -p name is not granted to this key, does not exist, or (for a machine user) names a server-restricted project. All three report the same message by design.
Exit 3The key is invalid, expired, or revoked, or no credential reached the runner. Check the secret name in your workflow.
Exit 4Neither an automation key nor a machine user token: an interactive reoclo login session.
Exit 7The runner could not reach the control plane. Usually transient; safe to retry.
A variable is empty in your scriptResolution succeeded but that key is not in a granted project. reoclo run cannot fail for a key it was never told about. For a machine user, this also happens when the project is granted but restricted to specific servers: run silently omits it instead of failing.

Your command’s exit code passes straight through, so a non-zero exit is not automatically a Reoclo failure, but the codes above are chosen not to collide with the ones scripts typically use. In particular exit 1 means your command failed, not that secrets were unavailable. Only reoclo run writes to stderr, so that stays the tiebreaker for the rare child that exits 6 or 7 itself.