Pipeline
A pipeline takes a commit from a Git repository and gets it running on this machine. It has two phases, both optional: build and release. Steps run in order; the first one that fails stops everything, except steps marked “continue even if it fails”.
Connecting a repository
Section titled “Connecting a repository”In Repositories → Connect repository:
- URL:
https://github.com/acme/api.gitorgit@github.com:acme/api.git. - Access:
- public: no credential;
- HTTPS token: on GitHub create a fine-grained token with “Contents: read” permission and use
x-access-tokenas the username; on GitLab use a deploy token; - SSH key: paste the private key of a read-only deploy key.
The credential is encrypted before being saved and can no longer be read from the interface. When editing, leaving the field empty keeps the existing one.
Where the checkout ends up
Section titled “Where the checkout ends up”Every repository has a persistent checkout at <SIRIO_DATA_DIR>/workspaces/<name> (by default
/var/lib/sirio/workspaces/<name>), reused across runs: the first time it’s a git clone,
later ones are just fetch + checkout on the requested commit. It’s never recreated from
scratch unless deleted by hand.
<name> comes from the repository’s name when it’s connected: lowercased, with anything other
than letters, digits and _ turned into - (My App → my-app), and a -2, -3, … suffix if
another repository already maps to the same directory. The Repositories page shows it under each
name. It’s chosen once and doesn’t follow later renames, because it’s also what Compose uses
as the project name when a compose step’s file sits at the root of the checkout — and so the
prefix of the containers it creates (my-app-web-1). Moving it would make the next deploy start a
second copy of the stack next to the old one. For the same reason, repositories connected before
this naming existed keep their repo-<id> directory.
The Detect from compose button in the pipeline editor, on the other hand, doesn’t touch this
checkout: it does a shallow, ephemeral clone in a temporary subfolder of the same workspaces/,
just to look for compose.yaml/compose.yml/docker-compose.yaml/docker-compose.yml in the
root, and deletes it right after — it doesn’t run or read anything else from the repository.
Step types
Section titled “Step types”Shell command
Section titled “Shell command”Run with sh -c in the checkout folder. Available variables:
| Variable | Content |
|---|---|
SIRIO_COMMIT |
full commit sha |
SIRIO_COMMIT_SHORT |
first 7 characters |
SIRIO_REF |
full ref, e.g. refs/heads/main |
SIRIO_BRANCH |
branch name |
SIRIO_RUN_ID |
run number |
SIRIO_PIPELINE, SIRIO_REPOSITORY |
configured names |
SIRIO_WORKSPACE |
checkout path |
DOCKER_HOST |
configured Docker socket |
npm cinpm run buildDocker image build
Section titled “Docker image build”Runs docker build. The placeholders {{commit}}, {{commit_short}}, {{branch}}, {{run}},
{{pipeline}}, {{repository}} are expanded in the tag:
acme/api:{{commit_short}}The Dockerfile and context are relative to the pipeline’s folder.
Docker Compose
Section titled “Docker Compose”Runs docker compose with a subcommand among up, down, pull, build, restart, stop.
With up Sirio adds --detach --remove-orphans, with down --remove-orphans.
Additional arguments (e.g. --pull always --wait) are appended after those, split on spaces
and passed to docker compose as separate arguments, without a shell: quotes aren’t interpreted,
so a single argument can’t contain a space. The editor shows the full command as it will run.
Container restart
Section titled “Container restart”Restarts a container by name via the Engine API, without going through the CLI. The name accepts the same placeholders as the image tag.
Backup
Section titled “Backup”Runs one of the site’s backup plans and waits for the snapshot: placed before the deploy steps, it saves the site as it was before the deploy changes it. It needs the pipeline linked to a site and a plan of that site, and fails the run if the backup fails. See docs/backups.md. An exported pipeline keeps the plan’s ID, which means nothing on another instance: pick the plan again after importing it there.
Automatic start from a webhook
Section titled “Automatic start from a webhook”Set the trigger to push or tag and give a branch or pattern (main, release/*, v*). On
creation Sirio generates the secret and shows it once only.
On the Git provider, configure:
- URL: the one shown in the pipeline’s Webhook tab;
- Content type:
application/json; - Secret: the generated one;
- Events: push only.
If you’ve lost the secret, generate a new one from the same tab and update it on the provider.
Calls with a missing or wrong signature get a 401 and end up in the activity log. Valid calls on a ref that doesn’t match the trigger get a 200 with an “ignored” outcome.
Execution and logs
Section titled “Execution and logs”Runs are serial: one at a time, queued. On a single machine, two simultaneous deploys do more harm than good.
Every run shows the log in real time (WebSocket) and keeps it on disk. Lines are tagged by
origin: system (Sirio), stdout, stderr. The last 50 runs per pipeline are kept, then older
logs and records are removed.
Rollback
Section titled “Rollback”Repeat this commit on a previous run re-runs the pipeline on the same sha, not on the branch’s latest commit. It’s the simplest way to roll back: the same build-and-release sequence, on the code that used to work.
For it to be a real rollback, steps must not depend on latest: use {{commit_short}} in image
tags, so every version stays distinct and reproducible.
Environment overrides
Section titled “Environment overrides”A deploy recreates containers from the image and the compose file, so a variable changed by hand
on a running container is lost at the next push. To keep one, set it from the panel instead:
Environment overrides (gear button in the container list, or in the container’s page), one
KEY=value per line. An override either adds a variable or replaces the value the image or the
compose file sets — useful for a secret you don’t want in the repository, or for a debug flag on
one host only.
Saving applies the change immediately by recreating the container (a brief interruption). The
rest of its configuration is kept as it is: mounts and volumes, including the anonymous ones an
image declares with VOLUME, networks and their aliases, so the other services of a compose
project still reach it by name, restart policy, labels. Saving an unchanged list restarts nothing.
Overrides are stored in Sirio’s database by container name, not by ID, and reapplied after every successful run: a pipeline that recreates the container gets them back right away, before the proxy is realigned. Containers that already carry them aren’t touched. In the container’s Environment tab, the variables set this way have an override badge.
Removing an override behaves differently depending on who owns the container:
- managed by Compose: Sirio first has Compose recreate it from the compose file and
.env, then applies the remaining overrides, so the original value comes back. Compose runs as Sirio’s own user, so it has to be able to read every file of the project: anenv_filecreated by root with mode0600stops it, and the error names the file (chgrp sirio FILE && chmod g+r FILEfixes it — thesiriouser already controls Docker, so this gives it nothing it couldn’t reach); - any other container: there is no source to restore the previous value from, so the last one applied stays in place. Set it explicitly if you need it back.
An override can’t remove a variable the image or compose file defines, only change its value.
Names follow the usual rule: letters, digits and _, not starting with a digit. Values are stored
in plaintext, like Docker itself stores a container’s environment; editing them requires the
edit permission on containers.
Export and import
Section titled “Export and import”Export on the Pipelines page downloads every pipeline you can see as one JSON file; the download icon on a row exports just that one. Import reads such a file back, on this instance or another one, and shows each pipeline before creating anything:
- the repository is proposed by matching the original Git URL (then the name), and has to be chosen when nothing matches: IDs mean nothing across instances, so the file only describes it;
- the site is proposed by name, falling back to the chosen repository’s site;
- the name can be changed, since it must be unique within a repository.
The import is all or nothing: if one pipeline is refused (invalid steps, a name already taken, a site you can’t create pipelines in), none is created and the row at fault is pointed out.
Webhook secrets are never exported. A pipeline with a push or tag trigger gets a new secret on import, shown once at the end together with its new URL: update both on the Git provider. Everything else travels as it is, step environment variables and build args included, so treat the file like the configuration it is.
{ "format": "sirio-pipelines", "version": 1, "exported_at": "2026-10-15T09:30:00Z", "pipelines": [ { "name": "deploy", "enabled": true, "trigger_type": "push", "trigger_ref": "main", "workdir": "", "build_steps": [{ "name": "test", "type": "shell", "command": "go test ./..." }], "deploy_steps": [{ "name": "up", "type": "compose", "compose_command": "up" }], "repository": { "name": "api", "git_url": "https://github.com/acme/api.git" }, "site": { "name": "acme" } } ]}The same endpoints work from a script, with a session: GET /api/pipelines/export (optionally
?ids=1,2) and POST /api/pipelines/import with the document above, each entry given a
repository_id and optionally a site_id.
Full example
Section titled “Full example”Repository acme/api, deploy with compose on every push to main.
Build
- test — shell:
go test ./... - image — docker build, tag
acme/api:{{commit_short}}, DockerfileDockerfile
Release
- tag current — shell:
docker tag acme/api:$SIRIO_COMMIT_SHORT acme/api:current - start — compose
up, filedocker-compose.yml
With docker-compose.yml pointing at acme/api:current, a rollback amounts to repeating a
previous run: it re-tags that commit’s image and restarts the stack.