Skip to content

Stages and jobs

A pipeline compiles to a flat DAG of jobs. Stages are grouping plus default edges. Cycles are errors.

stages:
- name: build
jobs:
- name: compile
runs_on: [linux, x64, docker]
container:
image: golang:1.27
env: { CGO_ENABLED: "0" }
user: "1000"
env: { FOO: bar }
steps:
- uses: slipway/checkout@v1
- run: go build ./...
- name: test
needs: [build] # all jobs of this stage wait for the build stage
jobs: [...]
- name: deploy-staging
needs: [test]
jobs:
- name: deploy
environment: staging
steps:
- uses: slipway/deploy@v1
with: { service: api, release: "${{ needs.build.outputs.version }}", strategy: rolling }
  • Stage names are unique. Job names are unique across the pipeline, so needs can reference them directly.
  • needs may reference jobs or stages. Omitted on a stage, it means “the previous stage”. Omitted on a job, it means “nothing in this stage”: jobs in a stage run in parallel unless they need each other.
  • needs: [] on a stage removes the implicit previous-stage edge.
  • if on a job evaluates after needs resolve. The functions success(), failure(), always() and cancelled() refer to the needed jobs. The default is success().
  • Limits: at most 500 jobs after matrix expansion per run (configurable per plan), 100 steps per job, 20 services per job.
Key Purpose
runs_on Runner labels the job must match; default from defaults.runs_on
container Run steps inside this image (docker and kubernetes executors)
services Sidecar containers with health checks: services
env Job-level environment
needs Dependencies
if Condition (CEL, braces optional)
timeout Duration like 20m, 2h. Maximum is plan-limited: Free 60m, Team 6h, Business 24h
retry { max, backoff, when }, where when is any of runner_lost, timeout, failure, any
allow_failure A failure does not fail the run
matrix Build matrix
concurrency { group, cancel_in_progress } at job level
cache, artifacts Caches and artifacts
outputs Exported as needs.<job>.outputs.<key>
environment Binds the job to a deploy environment

See the generated job reference for every field with types.

- name: compile
matrix:
os: [linux, windows]
go: ["1.26", "1.27"]
include: [{ os: linux, go: "1.27", coverage: true }]
exclude: [{ os: windows, go: "1.26" }]
fail_fast: true
max_parallel: 4
runs_on: [${{ matrix.os }}]
steps:
- run: go test ./...

Expansion happens at compile time. matrix.<key> is available in expressions, and the job’s display name becomes compile (linux, 1.27). fail_fast cancels the remaining combinations on the first failure; max_parallel caps concurrency.

retry: { max: 2, backoff: 30s, when: [runner_lost, timeout] }
timeout: 20m

Retries re-queue the job as a new attempt. Use runner_lost to survive spot-instance interruptions without masking real failures.

A job with environment: is a deploy job:

environment:
name: production
url: https://api.example.com # shown in the UI
service: api # the Service being deployed

Effects:

  1. The job waits for the environment’s protection rules (approvals, windows, freezes, policies) before it starts. While it waits its status is waiting_approval.
  2. environment.vars.* and environment-scoped secrets are injected.
  3. The run is recorded as a deployment of the given release, and your SCM receives a deployment status.
  4. slipway/deploy@v1 delegates to the deploy engine’s strategy (rolling, blue/green, canary with verification). The pipeline shows progress but does not implement the strategy.

You can also skip deploy jobs entirely: create a release with slipway/release-create@v1 and let lifecycles drive promotion. Both models are supported. The deploy domain is the source of truth for what is where.