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 run one after another, in the order they appear in the file. 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. The next job still runs. The run as a whole fails if any job failed, which means one broken job does not hide the result of the others.

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 and gh. 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.