Skip to content

Deploy from CI

The CLI is a thin HTTP client: it talks to a Kaio server and nothing else. A pipeline therefore needs no checkout of Kaio and no toolchain, just the published image with its entrypoint overridden.

Terminal window
docker run --rm --network host -e KAIO_ADDR \
--entrypoint kaio-cli ghcr.io/rgaidot/kaio:latest health
.github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: self-hosted
env:
KAIO_ADDR: ${{ secrets.KAIO_ADDR }}
KAIO: >-
docker run --rm --network host -e KAIO_ADDR
-v ${{ github.workspace }}:/w -w /w
--entrypoint kaio-cli ghcr.io/rgaidot/kaio:latest
steps:
- uses: actions/checkout@v4
- name: Refuse to deploy to a server that is not answering
run: $KAIO health
- name: Deploy
run: $KAIO stack deploy blog compose.yml --env-file .env.ci
- name: Fail if the stack did not come up
run: |
$KAIO --json status | jq -e --arg s blog '
[.[] | select(.stack == $s and .state == "running")] | length > 0
'
.gitlab-ci.yml
deploy:
stage: deploy
image: ghcr.io/rgaidot/kaio:latest
variables:
KAIO_ADDR: $KAIO_ADDR
script:
- kaio-cli health
- kaio-cli stack deploy blog compose.yml --env-file .env.ci
only:
- main

The image ships both binaries, so a job that runs in it calls kaio-cli directly. The entrypoint only needs overriding when the image is invoked with docker run.

health first. A server that is down fails the job before anything is written, instead of halfway through a deploy.

stack deploy is idempotent. It rewrites the compose file, recreates the containers and records a new version, so re-running the pipeline on an unchanged file is safe. A failed compose up restores the previous version on its own and the step fails, so a bad commit leaves the last working stack running.

Verify what you deployed. stack deploy returning 0 means the deploy was accepted, not that the containers stayed up. The --json status check above reads the containers back and fails the job if none of the stack’s are running.

The control plane forwards to any node in its registry, so the pipeline keeps talking to one address and names the node it wants:

Terminal window
kaio-cli --json node ls | jq -r '.[] | select(.status == "online") | .name'
- name: Deploy on every node that answers
run: |
for node in $($KAIO --json node ls | jq -r '.[] | select(.status=="online") | .name'); do
echo "deploying on $node"
$KAIO node "$node" run stack deploy blog compose.yml
done

A node removed from the registry drops out of the list and loses its route, so a decommissioned server cannot be deployed to by a pipeline that has not been updated.

Nothing schedules this for you. The loop is the whole of it, a node that was offline when the pipeline ran is skipped, and there is no rollback across hosts: each server deploys or restores its own previous version independently. If you need all-or-nothing across a cluster, that belongs in your pipeline, not in Kaio.

See Multiple servers for how servers join in the first place.

Terminal window
# Fail the build when a stack has a pending image update
kaio-cli --json stack ls | jq -e 'map(select(.update_available)) | length == 0'
# Fail on any critical CVE in a scanned image
kaio-cli --json image ls | jq -e '[.[].scan.summary.critical // 0] | add == 0'
# Report nodes that stopped answering
kaio-cli --json node ls | jq -r '.[] | select(.status != "online") | "\(.name) \(.last_error)"'

Kaio, built by Régis Gaidot