Writing a pipeline
A repository’s pipeline is one file, .squidci/pipelines.yaml, read at each
commit. This page builds one up; the reference
lists every setting.
# yaml-language-server: $schema=https://squidci.com/schema/pipelines.jsonStart the file with that line and editors check it as you type.
Components and sections
Section titled “Components and sections”The file is a map of components: parts of the repository built on their own. Each has its steps under one or more sections, and each section is one job.
web: base: ./web # where its steps run; the repository's root when left out check: - pnpm: lint test: - pnpm: test build: - pnpm: buildThe sections are check, test, build, publish, run and deploy. They
take the same steps, and differ in when they run:
| section | runs | on pull requests |
|---|---|---|
check |
at once | yes |
test |
at once | yes |
build |
at once | yes |
publish |
once its component’s check, test and build have passed | no |
run |
at once — for anything that fits nowhere else | no |
deploy |
per environment, once every check, test and build in the run passed | no |
Component names are letters, digits, ., _ and -, in segments joined by
/: web, services/api, runner/arm64.
A step is a command, or a map with one key naming a typed step.
api: build: - make build # a command, run by bash - pnpm: run build # pnpm, with Node and the lockfile set up - script: scripts/package.sh # a file of the repository, run by its extension - docker: # build the Dockerfile in base, and push it image: 123456789012.dkr.ecr.eu-west-2.amazonaws.com/api tag: latest- Commands run in order in one bash shell, started in the component’s
basewithset -eo pipefail: the job fails at the first command that fails, and acdorexportcarries on to the steps after it. A multi-line command (|) is one step. pnpm: <args>runspnpm <args>in a job SquidCI has set up for it: Node as.nvmrcor.node-versionpins it, thenpnpm install --frozen-lockfile, once, before the firstpnpmstep.script: <path>runs a file, relative tobase:.ts,.mts,.jsand the like with Node (TypeScript’s types stripped),.shwith bash, anything else by its shebang.docker: {image, tag, push}builds the Dockerfile inbaseasimage:tagand pushes it, logging in first when the registry is ECR.push: falsebuilds it to prove it builds.
Order: needs and after
Section titled “Order: needs and after”Jobs run at once unless told otherwise. needs makes a component wait
for every job of others — and brings what they made:
lib: build: - pnpm: build artifacts: - dist
app: needs: [lib] build: - pnpm: build # .squidci/artifacts/lib/dist is hereafter waits the same way, but brings nothing: an order, and only that.
Use it when one component must go first but uses nothing of the other — an
infra component deploying before the api on top of it.
Passing things between jobs
Section titled “Passing things between jobs”- Artifacts are paths a component’s
buildleaves, kept for the rest of the run: restored before its ownpublish,runanddeployat the same paths, and before each job of a component thatneedsit under.squidci/artifacts/<component>/<path>. - Values are short strings. A step appends
name=valuelines to the file$SQUIDCI_OUTPUTnames; a job thatneedsthis one gets each as$SQUIDCI_<COMPONENT>_<NAME>, and all of them as JSON in the file$SQUIDCI_INPUTSnames. Values are shown with the job, so never secrets.
image: publish: - docker: {image: public.ecr.aws/example/web, tag: latest} # exports digest
notify: needs: [image] run: - echo "pushed $SQUIDCI_IMAGE_DIGEST"Services
Section titled “Services”Containers that run beside each of a component’s jobs, on the job’s network, reached by name:
api: services: postgres: image: postgres:16 env: POSTGRES_PASSWORD: squid healthcheck: pg_isready -U postgres # the job starts once this passes test: - pnpm: test # DATABASE_URL=postgres://postgres:squid@postgres:5432/postgresThe runner
Section titled “The runner”runner says what a component’s jobs run in and on:
api: runner: image: ubuntu:24.04 # SquidCI's runner image when left out arch: arm64 # amd64 when left out vcpus: 2 # what of an instance each job reserves; 1 when left out spot: false # keep off spot instances; they prefer spot otherwiseSquidCI’s runner image is Ubuntu 24.04 with git, Docker, the AWS CLI, Node 22, 24 and 26 through nvm, pnpm and the AWS CDK. Jobs run as root in it, mapped to an unprivileged user on the worker, and may run Docker.
runner.lambda: nodejs26.x runs each step as code in a Lambda function
instead — quick, for small steps that call AWS or an API:
notify: runner: lambda: nodejs26.x run: - script: scripts/notify.tsCaches
Section titled “Caches”Each component gets a build cache, restored before its jobs and saved after
them when it changed: the caches of npm, pnpm, Yarn, pip, uv, Poetry, Gradle,
Maven and Go are kept there without any setting. cache: false turns it off
for a component.
Unchanged components
Section titled “Unchanged components”A component whose files — its base, its paths, the .squidci folder and
what it needs — have not changed since it last succeeded is not run again:
its jobs end unchanged, which counts as passing. paths names more
folders a component uses from elsewhere in the repository, so a change there
runs it too. Deploys always run.
Environment variables
Section titled “Environment variables”Every job has:
| variable | value |
|---|---|
CI |
true |
SQUIDCI_REPOSITORY |
owner/name |
SQUIDCI_COMMIT |
the commit’s full SHA |
SQUIDCI_BRANCH |
the branch |
SQUIDCI_REF |
the ref, refs/heads/<branch> |
SQUIDCI_ENVIRONMENT |
for a deploy, its environment’s name |
SQUIDCI_OUTPUT |
the file to append name=value lines to |
SQUIDCI_INPUTS |
with needs, the file holding what they exported |
AWS_PROFILE, AWS_REGION |
with aws: see AWS access |