actions.yml reference
Every key .rezee/actions.yml accepts, what it does, and what happens when you leave it out.
The file lives at .rezee/actions.yml in the root of your repository and is read from the commit being built. For how a run behaves once it is created, see Pipelines.
name: CI # optional
on: # optional
push:
branches: [master] # optional
paths: ["src/**"] # optional
jobs: # required
<job-id>:
name: Test # optional
image: node:22-alpine # optional
tools: # optional
bun: 1.2.24
steps: # required
- name: test
run: bun test
artifacts: # optional
- coverage/**Top level
| Key | Type | Default |
|---|---|---|
name | string | none |
on | object | every branch |
jobs | map | required |
name is accepted and is not shown anywhere yet - runs are identified by their commit and ref. Write it if it helps the next person reading the file; nothing depends on it.
A file with no jobs, or with an empty map, creates no run. So does a file that fails to parse - which is worth knowing, because the failure mode of a YAML typo is a push that quietly builds nothing rather than a run that goes red.
on.push
The only trigger. Tag pushes and pull requests do not start runs; a pull request's checks are the runs from the pushes to its branch.
branches
A list of glob patterns matched against the branch name, without the refs/heads/ prefix. Omitted or empty means every branch.
on:
push:
branches: [master, "release/*", "hotfix/**"]paths
A list of glob patterns matched against the files this push actually changed. If nothing matches, no run is created - and the check costs one diff of file names, not a runner.
on:
push:
paths:
- "apps/web/**"
- "packages/ui/**"
- "package.json"When the diff cannot be taken - a branch being created, a base that no longer resolves - the run happens. The safe reading of an unknown answer is to build.
Glob syntax
The same three wildcards in branches, paths and artifacts. A pattern must match the whole string, not a piece of it.
| Pattern | Matches |
|---|---|
* | any run of characters, stopping at / |
** | any run of characters, crossing / |
? | exactly one character, not / |
So apps/aki/** is every file under the CLI, apps/*/package.json is one per app, and apps/aki/VERSION is exactly one file. There is no negation and no brace expansion.
jobs
A map, not a list. The key is the job's id, and jobs run in the order they appear in the file - there is no needs:, because there is nothing to sequence: the order is the sequence.
name
What the job is called in the UI and the CLI. Defaults to the job's key, so jobs.test with no name shows as test.
image
The container image every step in this job runs in. Any image your runner can pull. Without one, steps run in alpine:3.22.
With tools: alongside it, image becomes the base the toolchain image is built on rather than the image steps run in directly. Without image, that base is debian:trixie-slim - every toolchain ships a glibc binary, and musl is not where you want to find that out.
tools
A map of toolchain to exact version. Rezee generates an image with those versions in it, builds it once, and caches it.
tools:
go: 1.25.6
bun: 1.2.24
node: 22.14.0
gh: 2.65.0
awscli: "2"| Tool | Version |
|---|---|
go | full release, e.g. 1.25.6 |
bun | release without the v, e.g. 1.2.24 |
node | release without the v, e.g. 22.14.0 |
gh | release without the v, e.g. 2.65.0 |
awscli | v2 only. "2" resolves at image-build time |
Naming a tool that is not on this list is an error that lists them, rather than a build that fails on a 404 from a generated URL.
Quote versions that look like numbers. YAML reads go: 1.25 as the number 1.25, and node: 22.10 the same way - which is not the release you meant. Rezee coerces what it is given back to a string, so this works more often than it should; write "1.25" anyway. A tool with no version at all is dropped, because latest is not something a cached image can mean.
steps
A list, in order. Each step is a name and a run, and run is a shell command - one line or a YAML block.
steps:
- name: install
run: bun install --frozen-lockfile
- name: check
run: |
bun run lint
bun run typecheck
bun testEach step is a fresh container over the same working directory. Files survive between steps; environment variables, shell functions, and anything installed into the image do not. A step whose run is empty is dropped - it would be a container started to run nothing.
The first non-zero exit ends the job. Steps after it are skipped, the job is marked failed, and the next job still runs.
artifacts
A list of glob patterns, relative to the working directory. Each pattern is collected and uploaded as its own zip, named after the pattern with the wildcards stripped - dist/** arrives as dist.zip.
artifacts:
- dist/**
- coverage.out
- "**/*.junit.xml"A pattern that matches nothing is skipped in silence. .git is always excluded. Artifacts are collected even when the job failed.
Environment
There is no env: key. A job's environment is the image's, plus this repository's secrets, which arrive as environment variables under the names you gave them. For anything else, export it in the step that needs it.
steps:
- name: publish
run: |
export NODE_ENV=production
npm publish # NPM_TOKEN comes from the repo's secretsA complete file
name: CI
on:
push:
branches: [master, "release/*"]
paths:
- "src/**"
- "package.json"
- ".rezee/actions.yml"
jobs:
check:
name: Lint and typecheck
tools:
bun: 1.2.24
steps:
- name: install
run: bun install --frozen-lockfile
- name: lint
run: bun run lint
- name: typecheck
run: bun run typecheck
test:
name: Test
tools:
bun: 1.2.24
steps:
- name: install
run: bun install --frozen-lockfile
- name: test
run: bun test --coverage
artifacts:
- coverage/**
build:
name: Build
tools:
go: 1.25.6
steps:
- name: build
run: go build -o dist/server ./cmd/server
artifacts:
- dist/**Next: Pipelines for how a run behaves, or the CLI reference for watching one from the terminal.
