Pipelines

CI in Rezee is one file in your repository. Push a commit and the run starts - no service to connect, no runner to provision, no second set of credentials.

The file

A pipeline is .rezee/actions.yml, committed at the root of your repository. It is read from the commit being built, so changing the pipeline and changing the code it builds is one push.

name: CI

on:
  push:
    branches: [master, "release/*"]

jobs:
  test:
    tools:
      bun: 1.2.24
    steps:
      - name: install
        run: bun install --frozen-lockfile
      - name: test
        run: bun test

A repository without the file simply has no CI. Nothing warns, nothing fails - the push lands and no run is created. The full schema is in the actions.yml reference.

What a push does

The moment your commits land, Rezee reads .rezee/actions.yml at that commit and decides whether it has a run to create:

  • Branch filter. on.push.branches is a list of glob patterns. No list means every branch.
  • Path filter. on.push.paths compares the files this push actually changed against a list of globs. If none match, no run is created and the diff is all it cost.
  • Quota. A workspace out of CI minutes gets a run recorded as quota_exceeded rather than silence, so the reason a build did not happen is visible in the same list as the builds that did.

Only branches trigger runs. A tag push delivers its webhook and creates no pipeline.

Jobs and steps

Jobs with no needs: are ready together, in the order they appear in the file. A job that names another waits for it. Steps within a job run in order too, and each one is a fresh container over the same working directory - so a file written by one step is there for the next, and a tool installed by one step is not.

A failing step ends its job immediately; the steps after it are skipped and the job is marked failed. Jobs that do not need it still run. Jobs that do are skipped. The run as a whole fails if any job failed.

Each job may name the image its steps run in. Without one, steps run in a small default image (alpine:3.22), which is enough for a shell script and not enough for a build - which is what tools: below is for.

jobs:
  lint:
    image: golangci/golangci-lint:v2.6.1
    steps:
      - name: lint
        run: golangci-lint run ./...

Toolchains

Naming a version under tools: gets you that toolchain without writing the install. Rezee generates an image with those versions in it, builds it once, and caches it - so the first run pays for the download and every run after it starts instantly.

jobs:
  build:
    tools:
      go: 1.25.6
      node: 22.14.0
    steps:
      - name: build
        run: go build ./... && node scripts/bundle.js

The tools available are go, bun, node, awscli, gcloud, gh and vercel. Naming anything else is an error that says so, rather than a build that fails four layers deep on a 404 from a URL nobody wrote.

Every version must be exact. There is no latest: a cached image cannot mean "whatever was current the day it was built", so a tool with no version is dropped rather than guessed at.

The build appears in the run as a step called toolchain, ahead of your own, so a cold cache is a minute you can watch rather than a minute of nothing.

Secrets

Secrets belong to a repository, are encrypted at rest, and reach a job as environment variables. Set them from the terminal or from repository settings.

$ printf %s "$TOKEN" | rezee secrets set NPM_TOKEN
$ rezee secrets list
$ rezee secrets rm NPM_TOKEN

Prefer stdin, as above. A value passed as --value lands in your shell history.

Values are redacted from logs. Every log line is scanned for every secret value on its way out of the runner, and a match is replaced with *** - so a script that echoes its environment does not publish it.

Only the repository's owner can read, set or remove secrets - not a workspace admin. rezee secrets rotate re-encrypts every value under a new key: the values stay exactly what they were and your pipelines keep working, but anything encrypted under the old key, including in a backup, stops being readable.

Artifacts

A job can declare paths to keep. Each pattern is collected from the working directory and uploaded as its own zip, downloadable from the run.

jobs:
  build:
    steps:
      - name: build
        run: go build -o dist/server ./cmd/server
    artifacts:
      - dist/**
      - coverage.out

Patterns support *, ? and **. The .git directory is always excluded. Artifacts are collected even when the job failed, because a failed build's partial output and its test report are usually the two things you want most.

$ rezee run artifacts
$ rezee run download --out ./artifacts

Watching a run

The run page streams logs live. From the terminal, the same run follows you back:

$ rezee run list                 # recent runs
$ rezee run watch                # follow the latest to its end
$ rezee run logs --watch         # tail the log lines
$ rezee run rerun                # queue the same commit again
$ rezee run cancel               # stop one that is still going

rezee run watch exits 0 when the run succeeded and 5 when it failed or was cancelled, so it drops into a script without parsing anything.

A re-run queues the same commit and ref again, and deliberately skips the on.push filters: branches and paths are rules about how a commit arrived, and a re-run is somebody naming that commit on purpose.

A cancel stops a run that is pending or running - from the run page, the CLI, or run_cancel over MCP. The run and every unfinished job and step under it read cancelled, and the runner puts its container down within a few seconds rather than building on to a result nobody will look at. It is not a failure: a cancelled run is nobody's code being wrong, which is why it is a word of its own.

Limits

  • Step timeout. A step gets 30 minutes, after which it is killed and the job fails with that written in the log.
  • Clone depth. Runs check out a single commit. A pipeline that needs ancestry - git describe, a commit count, a merge-base diff - should fetch what it needs in a step.
  • CI minutes. Usage is metered per workspace and shown in workspace settings. See runners and CI minutes for what happens at the limit and how dedicated runners change it.

Next: the actions.yml reference for every key the file accepts, or the CLI reference for the rest of rezee run.