# The `seekrit-run` launcher

`seekrit-run` is a compiled, single-file version of `seekrit run` built for
machines. It authenticates with a service token, fetches the bound
environment's encrypted secrets, **decrypts them locally**, layers them under
any `.env` files and the live process environment, and then `exec`s your
command. That's all it does — no config files, no caching, no daemon.

```bash
SEEKRIT_TOKEN=skt_… seekrit-run -- ./start-server
```

> **Note:** Use the Node `seekrit` CLI for human workflows (login, key setup, managing secrets). `seekrit-run` is **service-token only** and read-only at runtime: the smallest thing that can turn a token into an environment and hand off to your process.

## Degrades gracefully

Fetching seekrit secrets is **best-effort**. If the token is missing or
malformed, or the API can't be reached, `seekrit-run` logs a warning to stderr
and still runs your command with just the `.env` overlay and the live
environment:

```
seekrit-run: continuing without seekrit-managed secrets: could not reach the seekrit API: …
```

This means the same launcher works whether or not seekrit is reachable — locally
without a token, in CI, or in an offline sandbox — so you can wire it into an
image's entrypoint unconditionally. Only genuinely local problems stop it: a
usage error, an explicit `--env-file` that can't be read, or a command that
can't be started. The `seekrit run` subcommand of the Node CLI behaves the same
way.

Note what that degraded start actually gives you: the command runs, but
**without** its managed secrets. If your app needs them, it will fail somewhere
further in — usually deeper and less obviously than a failure at startup. Add
`--cache` when you would rather it keep running on the last secrets it saw.

## Surviving an outage with `--cache`

Off by default. With `--cache`, each successful resolve is written to disk and a
later run falls back to it when the API cannot be reached:

```bash
seekrit-run --cache -- ./start-server
```

```
seekrit-run: could not reach the seekrit API: … — using cached secrets fetched 6m ago
```

Only the **encrypted** response is stored — the same ciphertext and wrapped data
keys the API serves. Decrypting it still needs this token, so the file is no
more sensitive than the token beside it (it is written `0600` in a `0700`
directory). Live resolve is always tried first, and a *refused* resolve
(`401`/`403`) deletes the entry rather than falling back to it, so revoking a
token still takes effect on the next run. Entries expire after
`--cache-max-age` (default `24h`).

In a container, point it at a mounted volume so it survives a restart:

```bash
seekrit-run --cache --cache-dir /var/cache/seekrit -- ./start-server
```

Full behavior, including the two trade-offs it makes, is in the
[CLI reference](/docs/reference/cli#last-known-good-cache).

## Why use it instead of the CLI?

- **Tiny & static.** One ~1–2 MB file with no runtime dependencies — no Node,
  no OpenSSL, no system CA bundle. TLS roots are compiled in, so it runs
  unchanged in `distroless`, `alpine`, and `scratch` images.
- **Same behavior as `seekrit run`.** Identical layer precedence, `.env`
  parsing, and `${OTHER_SECRET}` [reference](/docs/guides/references) expansion;
  its decryption is verified bit-for-bit against the browser/CLI crypto.
- **Clean process model.** On Unix it replaces itself with your command
  (`execvp`), so signals and exit codes pass through exactly. On Windows it
  spawns, waits, and forwards the exit code.

## Install

The install script detects your OS and architecture, downloads the matching
binary from `run.seekrit.dev`, verifies its SHA-256 checksum, and drops it on
your `PATH`:

```bash
curl -fsSL https://run.seekrit.dev/install.sh | sh
```

It installs to `/usr/local/bin` if that's writable, otherwise `~/.local/bin`.
Override the destination, pin a version, or force a target with environment
variables:

```bash
# Pin a version and install somewhere specific.
curl -fsSL https://run.seekrit.dev/install.sh \
  | SEEKRIT_RUN_VERSION=0.2.0 SEEKRIT_RUN_INSTALL_DIR="$HOME/bin" sh
```

| Variable | Default | Description |
| --- | --- | --- |
| `SEEKRIT_RUN_VERSION` | `latest` | Version to install, e.g. `0.2.0`. |
| `SEEKRIT_RUN_INSTALL_DIR` | `/usr/local/bin` or `~/.local/bin` | Where to put the binary. |
| `SEEKRIT_RUN_TARGET` | auto-detected | Force a Rust target triple. |

### Manual download

Prefer to fetch it yourself? Every release is published under a versioned and a
`latest/` path, each with a `.sha256` alongside. On Linux the fully static
**musl** build runs anywhere (glibc, `alpine`, `distroless`, `scratch`):

```bash
# Pick your target: {x86_64,aarch64}-{unknown-linux-musl,unknown-linux-gnu,apple-darwin}
curl -fsSL -O https://run.seekrit.dev/latest/seekrit-run-aarch64-apple-darwin.tar.gz
curl -fsSL -O https://run.seekrit.dev/latest/seekrit-run-aarch64-apple-darwin.sha256
shasum -a 256 -c seekrit-run-aarch64-apple-darwin.sha256
tar xzf seekrit-run-aarch64-apple-darwin.tar.gz
install -m 0755 seekrit-run /usr/local/bin/
```

Windows ships as a `.zip` (`seekrit-run-x86_64-pc-windows-msvc.zip`).

## Usage

```bash
# Token from the environment (or a .env file); resolves org/app/env from it.
SEEKRIT_TOKEN=skt_… seekrit-run -- pnpm start

# Token via flag; everything after -- is the command, verbatim.
seekrit-run --token skt_… -- node --enable-source-maps server.js

# Swap one composed group's slice for this run.
seekrit-run --with auth-providers=staging -- ./app

# See where each variable resolved from (stderr; names only, never values).
seekrit-run --explain -- true
```

## Precedence

Highest wins — identical to `seekrit run`:

```
process env  >  .env files  >  app-environment secrets  >  group secrets
```

The live process environment always wins, so a value exported in the shell (or
by your orchestrator) overrides anything seekrit resolves.

## Options

| Flag | Default | Description |
| --- | --- | --- |
| `-t, --token <skt_…>` | `SEEKRIT_TOKEN` (env or `.env`) | Service token. |
| `--api-url <url>` | `SEEKRIT_API_URL` or `https://api.seekrit.dev` | API base URL. |
| `-e, --env-file <path>` | `.env` | A `.env` file to overlay (repeatable). |
| `--no-env-file` | | Do not load the default `.env`. |
| `--with <group=env>` | | Override one composed group's slice (repeatable). |
| `--explain` | | Print each variable's source to stderr. |
| `--no-interpolate` | | Leave `${OTHER_SECRET}` [references](/docs/guides/references) as literal text. |
| `--cache` | off | Fall back to the [last-known-good](#surviving-an-outage-with---cache) encrypted response when the API is unreachable. Also `SEEKRIT_CACHE=1`. |
| `--no-cache` | | Override `SEEKRIT_CACHE=1` for this run. |
| `--cache-dir <path>` | `SEEKRIT_CACHE_DIR`, else `$XDG_CACHE_HOME/seekrit` | Where cached responses live. |
| `--cache-max-age <d>` | `24h` | How stale a cached response may be and still be used. |

`HTTPS_PROXY` / `ALL_PROXY` are honored for egress-proxied networks.

## In a container

Keep Node out of your runtime image entirely. The simplest way is to copy the
binary out of the published image — `seekritdev/run` is multi-arch and is just
the static binary on `scratch` (~2 MB) — into a minimal runtime, 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"]
```

Pin a release tag (e.g. `seekritdev/run:0.3.0`) for reproducibility, or
`:edge` for the latest `main`.

Prefer not to depend on Docker Hub? Fetch the static binary in a build stage
instead — pin the version and verify the checksum for reproducible images:

```dockerfile
# --- fetch seekrit-run: pinned + checksum-verified --------------------------
FROM alpine:3 AS seekrit
ARG SEEKRIT_RUN_VERSION=0.2.0
ARG TARGET=x86_64-unknown-linux-musl
RUN apk add --no-cache curl && cd /tmp \
 && curl -fsSL -O https://run.seekrit.dev/v${SEEKRIT_RUN_VERSION}/seekrit-run-${TARGET}.tar.gz \
 && curl -fsSL -O https://run.seekrit.dev/v${SEEKRIT_RUN_VERSION}/seekrit-run-${TARGET}.sha256 \
 && sha256sum -c seekrit-run-${TARGET}.sha256 \
 && tar xzf seekrit-run-${TARGET}.tar.gz -C /usr/local/bin

# --- your runtime image: fully static, so distroless/static is enough -------
FROM gcr.io/distroless/static
COPY --from=seekrit /usr/local/bin/seekrit-run /usr/local/bin/seekrit-run
ENTRYPOINT ["seekrit-run", "--"]
CMD ["./start-server"]
```

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

> **Warning:** Inject at runtime, never at build time. Secrets fetched during `docker build` can be baked into an image layer. `seekrit-run` only ever puts values into the child process's environment — nothing touches disk.

## Exit codes

| Code | Meaning |
| --- | --- |
| `2` | Usage error (unknown flag, no command). |
| `1` | An explicitly named `--env-file` could not be read. |
| `127` | The command could not be started. |
| _child_ | Otherwise, the command's own exit code. |

A missing/malformed token, a revoked token or missing key grant, and network
failures are **not** fatal — `seekrit-run` warns and runs the command without
the managed secrets (see [Degrades gracefully](#degrades-gracefully)).

See the [CLI reference](/docs/reference/cli#seekrit-run-launcher) for the
condensed version, and [Service tokens](/docs/guides/service-tokens) for how to
mint the token this binary needs.
