Vetrix Docs

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:

  1. .ci/pipeline.yml
  2. .vetrix/pipeline.yml
  3. vetrix-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 v2not 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; use only: / except: on each job, inline any shared commands, and start dependencies inside commands: (e.g. via Docker-in-Docker) instead of services:.
  • Job-level script: / steps: / jobs: / runs-on: / services: / before_script: / after_script: — Vetrix calls its per-job command list commands: and schedules jobs via image:; there is no per-job services, setup-hook, or teardown-hook equivalent — prepend/append the commands to the job's own commands: instead.
  • artifacts: sub-keys when: / reports: / expire_in: — only artifacts.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 no when: to configure; retention is operator-managed, so there is no expire_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:

  1. If only is non-empty and the branch does not match any pattern → job is skipped.
  2. If the branch matches any except pattern → job is skipped.
  3. Otherwise → job runs.

Supported glob syntax:

  • Exact match: main
  • Prefix wildcard: release/* (matches release/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 failed state (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-report above 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 still failed, regardless of how many other jobs carry allow_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_failure only softens a job that fails. A cancelled job (e.g. a cancelled pipeline) is always treated as a hard failure regardless of allow_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