# 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`](https://github.com/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](https://docs.github.com/actions/security-guides/encrypted-secrets) named `SEEKRIT_TOKEN`.

```yaml
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](https://github.com/seekritdev/github-action#inputs) 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:

```yaml
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](/docs/guides/branches) 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.

```yaml
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`](/docs/guides/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:

```dockerfile
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](/docs/guides/run#in-a-container) also has a
Docker-Hub-free variant that downloads the binary from `run.seekrit.dev` and
verifies its checksum.

```bash
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`](https://hub.docker.com/r/seekritdev/cli),
or install it and wrap your command:

```dockerfile
# 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:

```bash
seekrit export --format dotenv > /run/secrets.env
```

> **Warning:** 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:

```yaml
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](/docs/guides/kubernetes). 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.
