Build your image on GitHub's runners, push it to your service's region, and roll out the new version — in one step, with no stored secret.
name: Deploy
on:
push:
branches: [main]
permissions:
contents: read
id-token: write # this is what lets GitHub vouch for the run
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: infrasutra/action@v1
with:
service: my-service # only needed if the repo links to severalNo secrets.*. No registry hostname. No credential to rotate, leak, or forget
to remove when someone leaves.
The obvious design is an INFRASUTRA_TOKEN repository secret. It works — and it
is the fallback below — but a stored token is long-lived, sits in repository
settings where it outlives whoever added it, and grants what it grants
everywhere, not just this repository.
With id-token: write, GitHub instead mints a short-lived assertion describing
this workflow run — repository, ref, workflow — signed by GitHub. Infrasutra
verifies it against GitHub's published keys and authorizes "a push to main of
your-org/your-repo", not "whoever holds this string". Nothing is stored, so
nothing can leak from a log or a compromised third-party action.
Link the repository to a service once (portal → your service → Settings →
Source), grant id-token: write, and you are done.
Create a key in the portal under Settings → Access Keys (max one-year life), store it as a repository secret, and pass it:
- uses: infrasutra/action@v1
with:
token: ${{ secrets.INFRASUTRA_TOKEN }}The same key works for the CLI's build and push. The action prefers OIDC
whenever id-token: write is granted, so adding the permission later retires the
secret with no other change.
The default mode builds, pushes, and releases. To release an image that already exists — one another job built, or a known-good digest you want back — skip the build:
- uses: infrasutra/action@v1
with:
mode: rollout
digest: sha256:2e6ace0b…No build, no push, no registry login — just the release of exactly that digest. The CI equivalent of the portal's Redeploy, pinned to one artifact.
| Input | Default | When you need it |
|---|---|---|
token |
— | Non-GitHub CI, or instead of OIDC |
mode |
build |
rollout to release an existing digest without building |
digest |
— | Required with mode: rollout |
service |
auto | Only if the repository links to several services |
context |
. |
Monorepos |
dockerfile |
Dockerfile |
Non-standard path |
build-args |
— | KEY=value per line |
wait |
true |
false to dispatch without waiting for health |
api-url |
https://app.infrasutra.com |
Self-hosted platform |
Outputs: image, digest, url.
GitHub runner control-api registry
│ │ │
├─ OIDC assertion (or token) ────►│ │
│ ├ verify vs GitHub JWKS │
│ ├ map repo → service │
│◄──── registry token (5 min) ────┤ scoped to one repository │
│ │
├─ docker push registry.<region>.…/tenant/service:sha ────────►│
│ verifies the JWT │
│ │
├─ POST /ci/deploy {digest} ─────►│ │
└ dispatch to the region
The registry token is scoped to a single repository and expires in minutes —
never a credential that could touch another tenant's images, and never delete.
The deploy is by digest, not tag, so "deploy what I just built" and "deploy
whatever the tag points at now" can't diverge under a concurrent push.
MIT