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. At run time it 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 no servers and no operations. That is not a limitation — resolution is gated by the grant you add next, not by a key’s operations. A key that can do nothing except read one secret project is exactly what belongs in CI, and it means a leaked key cannot be used to 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 — and, 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, install the CLI and use the same command:

    Terminal window
    curl -sSL https://get.reoclo.com/cli | bash
    REOCLO_AUTOMATION_KEY="$REOCLO_KEY" 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.

This is the one thing to internalise.

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
Fails, “no accessible secret projects”The key has no grants at all. Add one on the project’s Access tab.
Fails, “no accessible project matched”The -p name is not granted to this key, or does not exist. The two are reported identically by design.
Exit code 3The key is invalid, expired, or revoked.
Exit code 4Not an automation key — either a reoclo login session, or no credential reached the runner. Check the secret name in your workflow.
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.

A non-zero exit is not always a Reoclo failure: your command’s exit code is passed straight through, so a script exiting 1 looks like exit 1. The message on stderr is written only by reoclo run itself.