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 testA 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.branchesis a list of glob patterns. No list means every branch. - Path filter.
on.push.pathscompares 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_exceededrather 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.jsThe 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_TOKENPrefer 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.outPatterns 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 ./artifactsWatching 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 goingrezee 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.
