A database for your GitHub Actions
GitHub Actions has no native way to persist data between workflow runs — the cache expires after 7 days and can’t be updated in place, and artifacts need a run ID to fetch. PerSQL gives a workflow a real, persistent database instead: it survives across runs, and authentication is keyless — the workflow proves its identity with a GitHub OIDC token, so there’s no secret to store.
- Install the PerSQL GitHub App on the repo. (That’s the binding from your repo to your PerSQL namespace.)
- Give the job
id-token: write— the only permission needed.
permissions: id-token: write contents: read
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - id: db uses: persql/setup-db@v1 # connects keyless; outputs token + url - name: Use the database env: TOKEN: ${{ steps.db.outputs.token }} URL: ${{ steps.db.outputs.url }} run: | curl -sf -X POST "$URL/query" \ -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \ -d '{"sql":"CREATE TABLE IF NOT EXISTS runs (id INTEGER PRIMARY KEY AUTOINCREMENT, at TEXT DEFAULT (datetime('"'"'now'"'"')))"}' curl -sf -X POST "$URL/query" \ -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \ -d '{"sql":"INSERT INTO runs DEFAULT VALUES"}' # This count grows by one every run — the data persisted. curl -sf -X POST "$URL/query" \ -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \ -d '{"sql":"SELECT count(*) AS total_runs FROM runs"}'Without the action
Section titled “Without the action”The action is a thin wrapper over one HTTP call — you can do it by hand if you’d rather not add a dependency:
# Mint a GitHub OIDC token (id-token: write makes these env vars available)OIDC=$(curl -sf -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=persql" | jq -r .value)
# Exchange it for a short-lived, repo-scoped PerSQL tokencurl -sf -X POST https://api.persql.com/v1/actions/connect \ -H "Authorization: Bearer $OIDC"# -> { "data": { "token": "...", "url": ".../v1/db/<ns>/<db>", "database": "...", "namespace": "..." } }How it works
Section titled “How it works”- The workflow mints a short-lived GitHub OIDC token (
audience: persql) that proves this run is in repo X. - PerSQL verifies it against GitHub’s public keys, maps the repo to its namespace, and provisions a per-repo database (
gh-<owner>-<name>-actions). - You get back a short-lived token scoped to that one database — full control of it, no reach to anything else. Reads and writes go through the standard
/v1API.
Nothing is ever written to your repo’s secrets. The token expires on its own; the database persists.
- It’s SQLite. Great for run state, accumulating results, cron-style data collection, coordination between runs.
- Billing is usage-only against your namespace balance — you pay only for the requests, rows, and storage a run actually uses.
- Concurrent runs share the same database (it’s one DB per repo). Use SQL the way you would anywhere — e.g.
INSERT OR REPLACE,UNIQUEconstraints — if runs might race.