Pipeline YAML Reference
Pipelines are defined in a YAML file at the repository root. Vetrix probes the following paths in order and uses the first one it finds:
.ci/pipeline.yml.vetrix/pipeline.ymlvetrix-ci.yml
Files that do not match one of these paths are ignored. When none of the three resolves the Trigger / Retry endpoints respond 422 with a message that names every path probed, so the author knows exactly where to drop the file.
A pipeline consists of stages executed in order; within each stage all eligible jobs run in parallel.
Schema scope — this page documents the v1 schema
This page documents the v1 pipeline schema only. The ## Top-level keys,
## Job definition, and ### Unsupported keys from other CI systems
sections below describe v1 and nothing else. Unless a line says otherwise,
read it as "…under v1", not as a statement about Vetrix in general.
Vetrix has two live schema versions, selected by the top-level version: key:
version: |
Schema | Implementation |
|---|---|---|
| absent | v1 — what this page documents | internal/cicd/parser/v1.go, ParsePipeline |
1 |
v1 — what this page documents | internal/cicd/parser/v1.go, ParsePipeline |
2 |
v2 — not documented here | internal/cicd/parser/v2.go, ParseV2 |
| anything else | rejected | E_UNSUPPORTED_VERSION |
A v2 document is a different document shape, not v1 plus extras: jobs
move under a top-level jobs: mapping, and a top-level variables: block —
recognised under v1 (see ## Top-level keys below) — is rejected outright.
A v1-shaped file submitted with version: 2 fails with pipeline defines no jobs (missing jobs: block). For the v2 schema and how it differs from the
v1 rules below, see
authoring-vetrix-pipelines.md § 4.5; for a
worked v2 pipeline see
SBOM enablement § 3.
allow_failure (see Tolerating job
failures below) is the one field on
this page that holds under both schemas — it needs no v1/v2 caveat.
Unsupported keys from other CI systems
To help authors migrating from GitLab CI or GitHub Actions, Vetrix detects a handful of incompatible keys at parse time and returns a human-readable error instead of a generic "missing stage" failure. This list is v1-specific (incompatibleTopLevelKeys / incompatibleJobKeys / incompatibleArtifactsKeys in internal/cicd/parser/v1.go); v2 rejects a mostly-overlapping but not identical set — see the schema-scope note above.
- Top-level
on:/workflows:/include:/default:/before_script:/after_script:/services:— Vetrix has no equivalent; useonly:/except:on each job, inline any shared commands, and start dependencies insidecommands:(e.g. via Docker-in-Docker) instead ofservices:. - Job-level
script:/steps:/jobs:/runs-on:/services:/before_script:/after_script:— Vetrix calls its per-job command listcommands:and schedules jobs viaimage:; there is no per-job services, setup-hook, or teardown-hook equivalent — prepend/append the commands to the job's owncommands:instead. artifacts:sub-keyswhen:/reports:/expire_in:— onlyartifacts.paths:is recognised. Declared artifacts are collected when a job reaches a terminal state on both success and failure (only a cancelled job skips collection), so there is nowhen:to configure; retention is operator-managed, so there is noexpire_in:. See Artifacts below.
Minimal example
stages:
- test
unit-test:
stage: test
image: golang:1.22
commands:
- go test ./...
Top-level keys
| Key | Type | Required | Description |
|---|---|---|---|
stages |
list of strings | no | Ordered stage names; jobs in earlier stages complete before later stages begin. Defaults to [build, test, deploy] if omitted. |
variables |
map | no | Pipeline-level variables; merged with job-level variables |
All other top-level keys are treated as job definitions.
Job definition
<job-name>:
stage: <stage-name> # required — must be listed in stages
image: <docker-image> # required — runner pulls this image
commands: # optional — shell commands run in order
- <command>
variables: # optional — job-level env vars (override pipeline-level)
KEY: value
only: # optional — branch patterns; job runs only on matching branches
- main
- release/*
except: # optional — branch patterns; job is skipped on matching branches
- experimental/*
secret: false # optional — default false; true = all job variables are redacted in logs
allow_failure: false # optional — default false; true = a failure of this job is tolerated
environment: "" # optional — deployment target name (e.g. "production", "staging")
artifacts: # optional — files to preserve after job
paths:
- dist/
- coverage.out
Required fields
| Field | Description |
|---|---|
stage |
Must reference a stage declared in the top-level stages list |
image |
Docker image reference (e.g. golang:1.22, registry.example.com/org/image:tag) |
Optional fields
| Field | Type | Default | Description |
|---|---|---|---|
commands |
list of strings | [] |
Shell commands executed in order inside the container |
variables |
map | {} |
Job-level environment variables; override pipeline-level |
only |
list of strings | [] |
Branch glob patterns — job runs only on matching branches |
except |
list of strings | [] |
Branch glob patterns — job is skipped on matching branches |
secret |
boolean | false |
Redact all job variables from log output |
allow_failure |
boolean | false |
Tolerate this job's failure — see Tolerating job failures |
environment |
string | "" |
Deployment target name |
artifacts |
object | — | Files to collect after job completion |
Branch filters (only / except)
The only and except fields accept simple glob patterns to control which branches trigger a job.
Evaluation rules:
- If
onlyis non-empty and the branch does not match any pattern → job is skipped. - If the branch matches any
exceptpattern → job is skipped. - Otherwise → job runs.
Supported glob syntax:
- Exact match:
main - Prefix wildcard:
release/*(matchesrelease/1.0,release/hotfix, etc.) - Suffix wildcard:
*-deploy - Universal wildcard:
*(matches any branch)
deploy:
stage: deploy
image: alpine:3.19
only:
- main
- release/*
except:
- release/*-rc
commands:
- ./scripts/deploy.sh
Variables
Pipeline-level variables
Declared under the top-level variables key and available to all jobs:
variables:
GOFLAGS: "-mod=vendor"
CGO_ENABLED: "0"
Job-level variables
Declared under a job's variables key. These override pipeline-level variables with the same name:
build:
stage: build
image: golang:1.22
variables:
CGO_ENABLED: "1"
commands:
- go build -o dist/vetrix ./cmd/server
Variable interpolation
Variables can be referenced in image and commands fields using ${VAR_NAME} syntax. Undefined variables are left as-is (the literal ${VAR_NAME} string remains).
variables:
GO_VERSION: "1.22"
build:
stage: build
image: golang:${GO_VERSION}
commands:
- echo "Building with Go ${GO_VERSION}"
Predefined variables
The runner injects the following environment variables into every job container. The CI_* variables follow the de-facto convention used by GitLab CI / GitHub Actions so existing scripts that probe them keep working when migrating.
| Variable | Description |
|---|---|
CI |
Always "true" |
VETRIX_JOB_ID |
Vetrix job ID (UUID) |
VETRIX_PIPELINE_ID |
Vetrix pipeline ID (UUID) |
CI_PROJECT_DIR |
Workspace mount path inside the container — always /workspace |
CI_COMMIT_SHA |
Full commit SHA being built |
CI_COMMIT_REF_NAME |
Git ref (branch or tag) being built |
CI_COMMIT_REF_SLUG |
Slugified CI_COMMIT_REF_NAME (lowercase, [a-z0-9-], ≤63 chars) — safe for environment names, DNS labels, etc. |
CI_PIPELINE_ID |
Pipeline UUID (alias of VETRIX_PIPELINE_ID under the CI_ namespace) |
CI_JOB_ID |
Job UUID (alias of VETRIX_JOB_ID under the CI_ namespace) |
Workspace mount
The runner mounts a per-job host directory into the container at the path advertised by CI_PROJECT_DIR — currently the constant /workspace. The container is started with this path as its working directory so go build, npm install, and so on resolve relative paths the way pipeline authors expect.
Operators configure the host root via the RUNNER_WORKSPACE_DIR environment variable on the worker process. Each job receives a dedicated subdirectory <RUNNER_WORKSPACE_DIR>/<job-id> which is bind-mounted at /workspace. The runner refuses to mount any path that is not contained inside RUNNER_WORKSPACE_DIR after symlink resolution — defence-in-depth against escaped paths.
When RUNNER_WORKSPACE_DIR is unset (legacy / minimal deploys) no bind mount is created; CI_PROJECT_DIR still resolves to /workspace so jobs that read it don't crash, but the container starts with whatever filesystem the image ships.
Artifacts
Artifacts are files produced by a job that are collected by the runner and stored as pipeline artifacts. They are available for download from the UI.
build:
stage: build
image: golang:1.22
commands:
- go build -o dist/vetrix ./cmd/server
artifacts:
paths:
- dist/vetrix
The paths field accepts file globs relative to the workspace root.
When artifacts are collected. Declared artifacts are collected whenever
the job reaches a terminal state — both job success and job failure —
so a failing test job can still preserve its output (e.g. coverage.out)
for inspection. Only a cancelled job skips collection. There is no
when: sub-key to configure this (v1 rejects it — see Unsupported keys
from other CI systems above);
success/failure collection is unconditional, and retention is
operator-managed rather than author-configurable (no expire_in:).
Secrets
Mark a job as secret: true to redact all of its variables from log output. This is useful for jobs that handle sensitive credentials:
deploy:
stage: deploy
image: alpine:3.19
secret: true
variables:
DEPLOY_TOKEN: "${DEPLOY_TOKEN}"
commands:
- ./scripts/deploy.sh
Tolerating job failures (allow_failure)
By default every job hard-fails the pipeline: when a job exits non-zero, the
pipeline is marked failed, and any jobs in later stages are skipped (they never
run on a red build).
Set allow_failure: true on a job to make its failure tolerated (a "soft"
failure):
stages:
- test
- report
flaky-integration:
stage: test
image: golang:1.22
allow_failure: true
commands:
- go test -tags=integration ./...
publish-report:
stage: report
image: alpine:3.19
commands:
- ./scripts/publish.sh
When a job with allow_failure: true fails:
- The pipeline is not failed by it. The job stays in its terminal
failedstate (so you still see it red in the UI), but it does not stop the run. - Downstream stages still run. The pipeline advances past the tolerated
failure exactly as if the job had succeeded —
publish-reportabove still runs. - A pipeline whose only failures are tolerated ends
success(with a soft-failure warning surfaced alongside the result). If any non-tolerated job fails, the pipeline is stillfailed, regardless of how many other jobs carryallow_failure: true.
allow_failure is a per-job boolean and defaults to false — omitting it
preserves the historical hard-fail behavior. The value must be a literal boolean
(true / false); a non-boolean is rejected at parse time with a clear error.
Cancellation is never tolerated.
allow_failureonly softens a job that fails. A cancelled job (e.g. a cancelled pipeline) is always treated as a hard failure regardless ofallow_failure.
This flag also feeds the branch-protection pipeline gate: a branch gated on CI treats a soft-only pipeline as a pass, but a hard failure (or a cancelled job) as a block.
Environments
v1 only. The environment field names the deployment target for a job. This is informational and can be used for tracking deployments — it's accepted by the v1 parser but read by nothing today.
v2 rejects job-level environment: outright (droppedJobKeysV2["environment"] in internal/cicd/parser/v2.go) rather than accepting and ignoring it as v1 does: a version: 2 job carrying environment: is a hard parse error, because nothing reads the field — there is no pipeline_jobs column for it and no link to the deployments feed. See authoring-vetrix-pipelines.md § 4.5 for the full v1/v2 comparison.
deploy-prod:
stage: deploy
image: alpine:3.19
environment: production
only:
- main
commands:
- ./scripts/deploy.sh --env production
Complete multi-stage example
variables:
GOFLAGS: "-mod=vendor"
stages:
- lint
- test
- build
- deploy
lint:
stage: lint
image: golangci/golangci-lint:v1.57
commands:
- golangci-lint run ./...
test:
stage: test
image: golang:1.22
variables:
DATABASE_URL: postgres://vetrix:vetrix@localhost/vetrix_test?sslmode=disable
commands:
- go test -race -coverprofile=coverage.out ./...
artifacts:
paths:
- coverage.out
build:
stage: build
image: golang:1.22
commands:
- CGO_ENABLED=0 go build -o dist/vetrix ./cmd/server
artifacts:
paths:
- dist/vetrix
deploy:
stage: deploy
image: alpine:3.19
environment: production
secret: true
only:
- main
commands:
- ./scripts/deploy.sh