seekrit
Docs/CI/CD & containers

CI/CD & containers

The pattern for every automated environment is the same: provide a service token and either run your command with secrets injected or export them. No passphrase is involved.

Store the token in your platform's secret store as SEEKRIT_TOKEN. The token is bound to one application environment, so seekrit run/export need no --app/--env flags — it resolves its own org, app, environment, and composed groups.

GitHub Actions

The seekritdev/github-action action resolves your secrets, decrypts them on the runner, and injects them into the job — every later step sees them as environment variables. Store the token as an encrypted Actions secret named SEEKRIT_TOKEN.

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: seekritdev/github-action@v1
        with:
          token: ${{ secrets.SEEKRIT_TOKEN }}
      - run: ./deploy.sh # $DATABASE_URL, $API_KEY, … are set and masked

Every value is masked in the logs (::add-mask::) and decryption happens entirely on the runner — the API only ever returns ciphertext. Handy inputs: with (compose a group at a specific environment, like the CLI's --with), prefix, include/exclude, and set-outputs to expose values as step outputs instead of environment variables. See the action's README for the full list.

note

Prefer injecting into the job environment (the default) over step outputs — masked environment variables are a smaller surface than run-scoped outputs.

Want to wrap a single command instead of exporting to the whole job? Install the CLI and use seekrit run — the same pattern as every other CI system:

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      SEEKRIT_TOKEN: ${{ secrets.SEEKRIT_TOKEN }}
    steps:
      - uses: actions/checkout@v4
      - run: npm install -g @seekrit/cli
      - run: seekrit run -- ./deploy.sh    # secrets injected into the process

A config per pull request

Preview deploys usually need almost the shared config — with one or two values of their own (a database branch URL, a preview hostname). A branch config gives each PR exactly that: it inherits everything from dev and stores only the difference, then deletes itself.

SEEKRIT_BRANCH is read by seekrit run, seekrit export, and seekrit-run, so setting it once at the job level is enough. The token stays bound to dev — naming a branch of its own environment needs no new token and no new grants.

jobs:
  preview:
    runs-on: ubuntu-latest
    env:
      SEEKRIT_TOKEN: ${{ secrets.SEEKRIT_TOKEN }}   # bound to api/dev
      SEEKRIT_BRANCH: pr-${{ github.event.number }}
    steps:
      - uses: actions/checkout@v4
      - run: npm install -g @seekrit/cli
      - run: seekrit branch create "$SEEKRIT_BRANCH" --app api --from dev --ttl 3d
      - run: seekrit secrets set DATABASE_URL "$NEON_BRANCH_URL" --app api --env dev --branch "$SEEKRIT_BRANCH"
      - run: seekrit run -- ./deploy-preview.sh

  teardown:
    if: github.event.action == 'closed'
    runs-on: ubuntu-latest
    env:
      SEEKRIT_TOKEN: ${{ secrets.SEEKRIT_TOKEN }}
    steps:
      - run: npm install -g @seekrit/cli
      - run: seekrit branch delete "pr-${{ github.event.number }}" --app api

branch create is idempotent-friendly in practice — re-running the workflow on a new commit hits an existing-slug conflict, which you can ignore. The --ttl is the backstop for when teardown never runs: a cancelled workflow, a force-push, a dead runner. Nothing is left holding credentials.

Docker

Prefer injecting secrets at runtime, not baking them into an image. In containers the best fit is seekrit-run — a tiny static binary with no Node/OpenSSL/CA dependency, so it works in distroless, alpine, and scratch images. Copy it out of the published seekritdev/run image and make it the entrypoint:

FROM seekritdev/run:latest AS seekrit
FROM gcr.io/distroless/static
COPY --from=seekrit /seekrit-run /usr/local/bin/seekrit-run
ENTRYPOINT ["seekrit-run", "--"]
CMD ["./start-server"]

The seekrit-run guide also has a Docker-Hub-free variant that downloads the binary from run.seekrit.dev and verifies its checksum.

docker run --rm \
  -e SEEKRIT_TOKEN="$SEEKRIT_TOKEN" \
  your-image ./start-server

If Node is already in your image you can use the CLI instead — same behavior. The CLI ships as seekritdev/cli, or install it and wrap your command:

# entrypoint.sh
#!/bin/sh
exec seekrit run -- "$@"

If you must materialize a file (for tools that read .env), write it inside the container at startup and keep it out of any image layer:

seekrit export --format dotenv > /run/secrets.env
caution

Never seekrit export into a build stage that gets committed to an image layer. Fetch secrets at container start, into a tmpfs or process environment.

Kubernetes

Store the token in a Kubernetes Secret and reference it as an environment variable; run your app through the CLI:

env:
  - name: SEEKRIT_TOKEN
    valueFrom:
      secretKeyRef:
        name: seekrit-token
        key: token
command: ["seekrit", "run", "--"]
args: ["./start-server"]
tip

Prefer a declarative sync into native Kubernetes Secret objects — so pods consume secrets without any seekrit tooling in the image — with the External Secrets Operator. One helm install, then you write only ExternalSecret resources.

AI agent sandboxes

Ephemeral environments — like the throwaway sandboxes an AI coding agent spins up — are a natural fit: create a short-lived environment, grant a scoped token, and let the agent's process read secrets through the CLI without ever seeing long-lived credentials. Revoke the token when the sandbox is torn down.

note

A dedicated agent-proxy that swaps tokenized placeholders for real credentials on outbound requests (so an agent never sees secret values at all) is on the roadmap. The grant/wrap model already supports it — a proxy is just another principal.