Deploy from GitHub Actions
This guide wires Rune into a GitHub Actions workflow so every push to main (or every successful CI run, or whatever your trigger is) deploys via rune cast. The two pieces:
- A service-account token minted with
rune admin service create— scoped, revocable, and not a human user’s credential. - The
runestack/rune-cast-actioncomposite Action — installs the CLI, authenticates over stdin, runsrune cast, masks the token in logs.
1. Mint a service-account token
Section titled “1. Mint a service-account token”On a workstation that already has admin/root credentials loaded:
rune admin service create ci-stg \ --namespace stg \ --permissions cast \ --ttl 90d \ --description "GitHub Actions deploys to stg" \ --out-file ci-stg.tokenWhat this does:
- Creates a service-type subject
ci-stg. - Derives a per-service policy
ci-stg-cast— a copy of the built-incastpolicy with every rule pinned tonamespace: stg. The service account cannot reach into other namespaces. - Issues a bearer token (looks like
rune_<uuid>.<uuid>) valid for 90 days. - Writes the secret to
ci-stg.token(mode0600). This is the only time the secret is shown.
Cluster-wide token (no namespace pin): omit --namespace and the service account gets the cluster-wide built-in policy unchanged. Use this for tokens that need to deploy into many namespaces (e.g. a monorepo CI that cuts releases across stg and prod):
rune admin service create ci-all \ --permissions cast \ --ttl 90d \ --out-file ci-all.tokenTrade-off: a cluster-wide token is more powerful, so prefer one scoped token per namespace when your CI structure allows it.
2. Stash the token as a GitHub secret
Section titled “2. Stash the token as a GitHub secret”Per-environment secrets (recommended) so the stg job physically cannot see the prod token:
gh secret set RUNE_TOKEN --body "$(cat ci-stg.token)" --env staginggh secret set RUNE_TOKEN --body "$(cat ci-prod.token)" --env production
# Then shred the local copies.shred -u ci-stg.token ci-prod.token # or `rm -P` on macOSSet the runed address as a (non-secret) repo or environment variable:
gh variable set RUNED_HOST --body "runed.example.com:443" --env staging3. Wire the Action into your workflow
Section titled “3. Wire the Action into your workflow”.github/workflows/deploy.yml:
name: Deployon: push: branches: [main]
jobs: stg: runs-on: ubuntu-latest environment: staging steps: - uses: actions/checkout@v4
- uses: runestack/rune-cast-action@v1 with: server: ${{ vars.RUNED_HOST }} token: ${{ secrets.RUNE_TOKEN }} source: infra/runeset/casts/external-service.yaml values: infra/runeset/values/stg.yaml namespace: stgThat’s it. The Action:
- Installs the matching Rune CLI (caches by version under
$RUNNER_TOOL_CACHE). - Calls
::add-mask::on the token so any echo is***. - Pipes the token over stdin:
printf '%s' "$token" | rune login ... --token-stdin. The token never appears on argv. - Runs
rune cast <source>with every other input mapped 1:1 to the matching CLI flag.
Common patterns
Section titled “Common patterns”Multiple values files / --set overrides
Section titled “Multiple values files / --set overrides”Multi-line inputs split on newline and become repeated flags:
- uses: runestack/rune-cast-action@v1 with: server: ${{ vars.RUNED_HOST }} token: ${{ secrets.RUNE_TOKEN }} source: infra/runeset/ recursive: true values: | infra/runeset/values/base.yaml infra/runeset/values/prod.yaml set: | app.tag=${{ github.sha }} app.replicas=3 namespace: prod create-namespace: trueDry-run on PRs, real deploy on merge
Section titled “Dry-run on PRs, real deploy on merge”- uses: runestack/rune-cast-action@v1 with: server: ${{ vars.RUNED_HOST }} token: ${{ secrets.RUNE_TOKEN }} source: infra/runeset/casts/external-service.yaml values: infra/runeset/values/stg.yaml namespace: stg dry-run: ${{ github.event_name == 'pull_request' }}Pin the Rune CLI version
Section titled “Pin the Rune CLI version”By default the Action installs the latest release. Pin per workflow if you want lockstep with a known-good cluster version:
- uses: runestack/rune-cast-action@v1 with: version: v0.0.1-dev.26 # or "latest" # ...Install only (no deploy)
Section titled “Install only (no deploy)”Sometimes you just want rune get, rune logs, etc. in CI without a full cast. Use the runestack/rune-setup-action primitive directly:
- uses: runestack/rune-setup-action@v1 with: version: latest
- run: rune get services -n stg env: RUNE_SERVER: ${{ vars.RUNED_HOST }} RUNE_TOKEN: ${{ secrets.RUNE_TOKEN }}Rotation and revocation
Section titled “Rotation and revocation”# Inspect what's out there.rune admin token list
# Revoke immediately if a token leaks.rune admin token revoke <token-id>
# Re-mint (idempotent — same name upserts the subject).rune admin service create ci-stg \ --namespace stg --permissions cast --ttl 90d \ --out-file ci-stg.tokengh secret set RUNE_TOKEN --body "$(cat ci-stg.token)" --env stagingshred -u ci-stg.tokenSet a calendar reminder a week before --ttl expiry, or run service tokens with --ttl 0 (no expiry) and rotate on incident only — your call.
See also
Section titled “See also”rune admin service— full flag reference.rune login --token-stdin— the auth primitive the Action uses.- Identity & RBAC — how policies, subjects, and tokens fit together.
runestack/rune-cast-action— every input documented in the README.