DocsBuild
Pipelines and pipes
Write CI and CD for Acahti in .acahti/pipelines/, call official pipes, and pass secrets without putting them in git.
A pipeline is one run. Each YAML file in .acahti/pipelines/ is a job in that run, and each job has named steps. A pipe is an official, reusable step: pipe: <name>@v1 with inputs under with:.
Before the Runner starts a job, the gateway expands it: pipe: becomes a call to acahti-pipe <name>, mapped secrets become step environment, and every step receives the run's Acahti identity: ACAHTI_USER, ACAHTI_TOKEN, ACAHTI_ROOT_URL and ACAHTI_ORG. That identity is the Acahti account of the head commit's author (falling back to the account that started the run); a step's own environment: value wins. The Runner only ever sees expanded YAML.
Writing a job#
A step is either pipe: with with:, or plain commands: for one-offs. when, depends_on, and labels pass through unchanged. Do not vendor helper scripts into the repository and do not write uses:.
steps:
test:
image: node:22
commands:
- npm ci
- npm test
Jobs order by depends_on between files, parents first. If a job declares concurrency:, its group is scoped to the repository, so group: deploy in two repositories does not serialize them. Leave concurrency out to run freely.
In commands:, write $VAR for step environment. ${VAR} is interpolated before the shell starts and is empty unless it is job context, such as ${CI_COMMIT_SHA} or ${CI_COMMIT_TAG}.
Trigger runs with git push, a tag, or a pull request. Git tags are vX.Y.Z; image tags drop the v (${CI_COMMIT_TAG#v}).
Secrets#
Pipeline secrets are credentials for systems outside Acahti: registries, clusters, object storage. Admins add them in the web app (Admin → Org secrets, or a repository's Secrets page) or with secret_put. Names are lowercase snake_case. Values are never listed or returned.
A step opts in. List a secret when its name is the variable the pipe wants; map it when the names differ:
secrets:
KUBECONFIG: kubeconfig_prod
DOCKER_USERNAME: registry_username
DOCKER_PASSWORD: registry_password
Do not put file paths to credentials in with:. Acahti's own npm and PyPI registries use the run's identity, so never create a token secret for them.
Official pipes#
docker-build#
Build images with BuildKit and push them, or build cache-only for CI.
| Input | Required | Description |
|---|---|---|
images |
yes | One image per line. First field is the primary tag. Optional KEY=VAL: also extra tags (comma-separated), context (default .), file (Dockerfile), anything else is --build-arg. Job identity is written as BuildKit secrets id=npmrc and id=netrc (HTTP Basic from ACAHTI_USER / ACAHTI_TOKEN). Dockerfiles mount id=npmrc at /root/.npmrc and id=netrc at /root/.netrc. Default --network=host on buildx. CODEUP_NETRC, when set, is passed as id=codeup_netrc. |
push |
true (default) is buildx --push; false is --output=type=cacheonly (CI) | |
network |
docker buildx --network (default host) | |
builder |
buildx builder name (default acahti, docker-container + host network) |
docker-gc#
Bound shared BuildKit cache and report disk pressure. Runs every 15 minutes on the Runner.
docker-login#
Log in to a container registry for later steps in the job. Secret DOCKER_PASSWORD.
| Input | Required | Description |
|---|---|---|
registry |
yes | Registry hostname. An http:// prefix is the same as http: true. |
http |
true if the registry is HTTP/insecure (BuildKit). Default false (HTTPS). | |
username |
Registry user; password is DOCKER_PASSWORD (or DOCKER_USERNAME + DOCKER_PASSWORD) |
helm#
Install or upgrade a Helm release. Secret KUBECONFIG holds the kubeconfig document.
| Input | Required | Description |
|---|---|---|
release |
yes | |
namespace |
yes | |
chart |
Chart path relative to the workspace (default chart) | |
files |
Extra -f values files, one per line, relative to the workspace | |
set |
Extra --set lines (foo=bar or --set foo=bar) | |
timeout |
Helm --timeout (default 5m) | |
take_ownership |
true to pass --take-ownership | |
jump |
SSH host, reachable from the Runner, that runs helm when the cluster API is private |
npm-publish-acahti#
Build an npm package and publish it to this org's Acahti registry.
| Input | Required | Description |
|---|---|---|
path |
yes | Package directory (package.json). Installs, builds, packs, and PUTs to this Acahti org's npm. |
image |
Node image (default node:22-alpine). Private registry images need a prior docker-login. |
oci-gc#
Prune old tags from OCI repositories, keeping latest and the newest few.
| Input | Required | Description |
|---|---|---|
repos |
yes | OCI repositories to prune (no tag), one per line |
keep |
Non-latest tags to keep (default 2). latest is always kept. |
oss-put#
Upload files to Alibaba Cloud OSS. Secrets OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET.
| Input | Required | Description |
|---|---|---|
endpoint |
yes | |
bucket |
yes | |
prefix |
yes | Object key prefix (no oss://) |
files |
yes | Local files to upload, one per line (workspace-relative) |
pypi-publish-acahti#
Build a Python package and publish it to this org's Acahti registry.
| Input | Required | Description |
|---|---|---|
path |
Directory with pyproject.toml (default .). Builds and PUTs to this Acahti org's PyPI. | |
image |
Python image (default python:3.12-slim-bookworm). uv is installed if missing. Private registry images need a prior docker-login. |
uv#
Run commands in a job-local Python environment with uv.
| Input | Required | Description |
|---|---|---|
project |
Directory with pyproject.toml (default .) | |
run |
yes | One command per line; each is uv run --project |
wait-http#
Wait until URLs stop answering 502, 503, 504, or nothing.
| Input | Required | Description |
|---|---|---|
urls |
yes | URLs to poll, newline or semicolon separated |
timeout |
Seconds to wait (default 180) | |
user_agent |
User-Agent header for the probes |
Examples#
CI that builds an image without pushing it:
steps:
build:
pipe: docker-build@v1
with:
push: false
images: |
app:${CI_COMMIT_SHA}
CD that builds, deploys, and prunes old tags, one deploy at a time:
concurrency:
limit: 1
group: deploy
steps:
login:
pipe: docker-login@v1
with:
registry: registry.example.com
secrets:
DOCKER_USERNAME: registry_username
DOCKER_PASSWORD: registry_password
build:
depends_on: [login]
pipe: docker-build@v1
with:
images: |
registry.example.com/app:${CI_COMMIT_SHA} also=registry.example.com/app:latest
deploy:
depends_on: [build]
pipe: helm@v1
with:
release: app
namespace: default
files: |
chart/values.yaml
set: |
image.tag=${CI_COMMIT_SHA}
secrets:
KUBECONFIG: kubeconfig_prod
gc:
depends_on: [deploy]
pipe: oci-gc@v1
with:
repos: |
registry.example.com/app
Publish a package to the org's registry on a tag:
when:
event: tag
steps:
publish:
pipe: pypi-publish-acahti@v1
End-to-end tests after a deploy job named cd:
depends_on: [cd]
steps:
e2e:
pipe: uv@v1
with:
run: |
argos run all --env bj-test --dash
The step runs with the run's ACAHTI_ROOT_URL and ACAHTI_TOKEN, so --dash streams the run to Argos without a secret. When a job or one of its steps has a name containing e2e or argos, the pipeline page links the run the CLI reports (argos <sid>) as Argos.
Link pipelines to work#
When a pipeline's commit message, branch or pull request title names a todo or ticket (todo#123, ticket#45, todo-123, todo/123, ticket/45, T-123), Acahti links the pipeline, and the pull request it ran for, to that item. A pull request is also linked by its own title, body and head branch. The pipeline then appears in the item's Track flow under Build, and the pull request under Review.
For agents: pipelines.mdEdit this page