Skip to content

GitHub Action

statio ships a GitHub Action, published on the Marketplace as accentiostudios/statio@v1. One step does the whole pipeline: it builds and pushes your image, cosign-signs it keyless, installs the pinned statio binary, joins your tailnet as an ephemeral tag:ci node, signs the deploy payload with the same OIDC identity, and sends the signed envelope to your agent — no separate build-push, cosign-installer or cosign sign steps to wire up.

- uses: actions/checkout@v4
- uses: accentiostudios/statio@v1
with:
target: statio.your-tailnet.ts.net # the agent's MagicDNS host (= signed audience)
service: api # must be accepted on the server (statio app add)
image: ghcr.io/accentiostudios/api # the action builds+pushes here; the agent runs it
ts-oauth-client-id: ${{ secrets.STATIO_TS_OAUTH_CLIENT_ID }} # CI's tag:ci OAuth client
ts-oauth-secret: ${{ secrets.STATIO_TS_OAUTH_SECRET }}
env: | # optional per-deploy env, from GitHub Secrets
DATABASE_URL=${{ secrets.DATABASE_URL }}

Already build the image in your own steps? Pass digest: ${{ steps.build.outputs.digest }} and the action skips building (it still signs + deploys). Add sign: false if you also sign it yourself.

A complete .github/workflows/deploy.yml — exactly what statio init repo generates when your repo has no CI yet:

name: deploy
on:
push:
branches: [main]
workflow_dispatch:
inputs:
digest:
description: "Existing image digest to (re)deploy — use for rollback"
required: false
# id-token: keyless cosign (OIDC); packages: push to GHCR; contents: checkout.
permissions:
id-token: write
packages: write
contents: read
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# One step builds + pushes + signs the image, then signs the payload and deploys.
- uses: accentiostudios/statio@v1
with:
target: statio.your-tailnet.ts.net
service: api
image: ghcr.io/accentiostudios/api
digest: ${{ inputs.digest }} # empty on push → the action builds; set to redeploy
ts-oauth-client-id: ${{ secrets.STATIO_TS_OAUTH_CLIENT_ID }}
ts-oauth-secret: ${{ secrets.STATIO_TS_OAUTH_SECRET }}
env: |
DATABASE_URL=${{ secrets.DATABASE_URL }}

Already have a workflow? Don’t copy the whole file — statio init repo prints just the step to add to your existing one, and never touches your file.

InputRequiredWhat it is
targetyesThe agent’s MagicDNS host (e.g. statio.your-tailnet.ts.net). It is the signed audience — the deploy is bound to that server.
serviceyesThe app slot; must be accepted on the server (statio app add).
imageyesYour image repository; the action builds + pushes here, and it must match statio app add --image (repo-equality).
digestnoDeploy this exact digest and skip building. Leave empty to let the action build & push (steps.build.outputs.digest from your own build, or an old digest for rollback).
dockerfilenoDockerfile path when the action builds (default Dockerfile).
contextnoBuild context when the action builds (default .).
image-tagnoTag to push the built image under (default ${{ github.sha }}; the deployed reference is always the digest).
signnocosign-sign the image (default true). Set false only if you sign it yourself.
registry-username / registry-passwordnoRegistry login when the action pushes. Default to the GitHub actor + GITHUB_TOKEN (GHCR). Set both for Docker Hub / other registries.
envnoPer-deploy overrides, KEY=${{ secrets.KEY }} lines. GitHub masks them.
statio-filenoPath to statio.yaml (default statio.yaml).
ts-oauth-client-idyesCI’s tag:ci OAuth client id (the STATIO_TS_OAUTH_CLIENT_ID secret).
ts-oauth-secretyesCI’s tag:ci OAuth client secret (the STATIO_TS_OAUTH_SECRET secret).
statio-versionnoBinary version to download (default v1; a bare major, exact vX.Y.Z, or latest).
timeoutnoDeploy timeout (default 5m).
strictnoTreat success_degraded as a failure (default false).
preflightnoRun a read-only readiness check before deploying (default true). It verifies the app’s host port is free and — if your statio.yaml asks for a public domain — that NPMplus is configured, reachable and the domain is allowlisted, without changing anything. A failure stops the job before the deploy mutates the server. Set false to skip.

deploy_seq (anti-replay) is set by the Action itself from github.run_number — don’t configure it.

The preflight step runs after the tailnet join and before the deploy, sending the same signed payload to the agent’s read-only /preflight endpoint. It is the cheap, fail-fast guard against hidden crashes (a port another app already owns; a proxy/DNS the repo requests but the server was never set up for).

@v1 is a moving major tag: it always points at the latest stable 1.x release. Pin an exact version with @v0.1.0 if you prefer reproducible builds. The statio-version input keeps the downloaded binary in lockstep with the wire schema.

The Action is not a separate repo. Its action.yml lives at the root of accentiostudios/statio; the same repo ships the CLI, the agent and the binary releases. That’s why a single uses: accentiostudios/statio@v1 is all you need — no extra install step for the tool itself.