swarmcli-cd

module
v1.0.0-rc5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: Apache-2.0

README

SwarmCLI CD

GitOps continuous delivery for Docker Swarm — reconcile your swarm from Git, the way Argo CD does for Kubernetes.

Status: Phase 2, release candidates. The pull loop works end to end — fetch, render, plan, diff, apply, prune, drift detection and health — and is exercised against a real swarm by the integration tests. driftDetection: live has landed: a docker service update made behind the controller's back is seen, and on an automated application corrected. So has app-of-apps, which puts the application set itself in git. Webhook triggers and sync waves are the rest of Phase 2; the licensed companion is Phase 3. Everything tagged so far is a release candidate, so expect rough edges and pin the tag you deploy: :latest never moves to a prerelease, so no such image tag exists yet. The design, decisions and phase plan live in issue #1.

New here? Start with the getting-started guide.

Why

SwarmCLI already ships the hard half: swarmcli charts is a Helm-analogue for Swarm — templated packages, values schemas, repository indexes with digest verification, dependency pre-flight, and revision history stored in Swarm's own Raft store. swarmcli charts apply converges a swarm to a file you commit.

What is missing is the pull half: something that watches Git, reconciles continuously, detects drift, prunes what left the repo, and shows what is actually running versus what should be. Today that gap is filled by CI running charts apply after a merge — which means CI holds cluster write credentials and nothing corrects drift between deploys.

What makes this different

GitOps for Swarm is not greenfield — see the survey in #1. Several tools deploy a compose file from a Git repo, and do it well. The gaps nobody has closed for Swarm are:

  • a real diff between the compose-derived desired ServiceSpec and the live one, shown before it is applied
  • sync and health status per stack and per service
  • pruning networks, configs and secrets — not just services
  • automatic rollback on failed convergence, using Swarm's own update_config.failure_action: rollback and PreviousSpec, which the platform gives away for free and every existing tool ignores
  • charts as a first-class source, with the revision history and rollback that already exist

Installing

Released binaries (Linux and macOS, amd64 and arm64) are attached to each release; the controller image is eldaratech/swarmcli-cd. The same binary is both the controller and its client, so a laptop needs only the archive.

Building it instead:

go build -o swarmcli-cd ./cmd/swarmcli-cd
./swarmcli-cd version

Requires Go 1.26+. A plain go build leaves the chart-engine version unstamped, which makes every chart compatibility check report Unknown — fine for development, not for anything that deploys. See RELEASING.md.

Using it

One binary runs the controller and talks to it. The controller reconciles and serves the API; every command that inspects it is a client of that API, so anything the CLI can show, the TUI view and the web UI will show through the same endpoints.

# In the swarm, on a manager node, with docker.sock mounted:
export SWARMCLI_CD_ADMIN_TOKEN_FILE=/run/secrets/swarmcli-cd-token
swarmcli-cd controller --config /etc/swarmcli-cd/applications.yaml

# From anywhere that can reach it:
export SWARMCLI_CD_SERVER=http://controller:8080
export SWARMCLI_CD_ADMIN_TOKEN=...

swarmcli-cd app list                 # sync state and health, one row each
swarmcli-cd app get edge             # releases and their services
swarmcli-cd app diff edge            # what a sync would change
swarmcli-cd app history edge         # each release's revisions
swarmcli-cd app sync edge --wait     # reconcile now; non-zero if it failed
swarmcli-cd status                   # the controller itself: where its app set comes from

# No controller, no swarm — this one reads a file:
swarmcli-cd validate --file applications.yaml   # non-zero if the set is invalid

Add -o json to any read for the controller's own response, unmodified — that is the form to script against. The admin token never comes from a flag: a token in argv is a token in ps and in the shell history.

Run swarmcli-cd controller --help or swarmcli-cd app help for the rest.

Documentation

Deploying it

Per D2 the controller runs in the swarm, on a manager node, and reaches the daemon through the mounted socket. The image carries no docker binary: the applier is built on the moby client rather than shelling out to docker stack deploy, which is also why it can diff, prune and roll back things that command cannot.

# Start from examples/applications.yaml and edit it for your repositories.
docker config create swarmcli-cd-applications ./applications.yaml
printf '%s' "$(openssl rand -hex 32)" | docker secret create swarmcli-cd-token -
docker stack deploy -c stack.yml swarmcli-cd

Both a config and a secret are immutable in Swarm, so changing either means creating a new one and updating stack.yml. That is right for the controller's bootstrap, which rarely changes — and optional for the application set, which can live in git instead:

swarmcli-cd controller \
  --appset-repo https://github.com/your-org/apps.git \
  --appset-revision main \
  --appset-path apps/applications.yaml

Then adding, removing or retuning an application is a commit rather than a redeploy; a commit that does not validate leaves the running set untouched and is reported by swarmcli-cd status. The mounted-config mode stays the default and the air-gap fallback. See configuration § where the app set lives.

stack.yml does not publish the API port. The controller holds root-equivalent access to the swarm behind one shared bearer token over plaintext HTTP, so publishing it on a node with a public address puts the swarm on the internet. Reach it from inside the swarm, or tunnel:

ssh -L 8080:127.0.0.1:8080 manager
Configuration

The tables below are the quick reference; docs/configuration.md is the full one, including every field of the applications file.

The controller takes flags; credentials come from the environment, because they arrive as Docker secrets and a flag would put them in docker service inspect output and in argv.

Flag Default
--config /etc/swarmcli-cd/applications.yaml the applications file, delivered as a Docker config
--listen :8080 API listen address
--data /var/lib/swarmcli-cd repository clones and the chart cache, on a volume
--appset-repo --appset-revision --appset-path source the application set from a git repository instead
--appset-dir source it from a directory a git-sync sidecar keeps current
--appset-interval 3m how often the application set is re-read
--prune off delete the resources of an application that has left the application set, instead of leaving the stack running and reporting it as orphaned
--prune-volumes off extend --prune to named volumes, the one part nothing can restore; requires --prune
--controller-id default this controller's identity, stamped on every release it installs. Two controllers on one swarm must differ, or each treats the other's applications as departed
--log-level info debug, info, warn or error
--log-format text text or json; everything goes to stderr through one handler either way
Environment
SWARMCLI_CD_ADMIN_TOKEN_FILE API admin token, read from a file — the Docker-secret form
SWARMCLI_CD_ADMIN_TOKEN API admin token, given directly
SWARMCLI_CD_GIT_USERNAME git username; forges usually ignore it, GitHub wants it non-empty
SWARMCLI_CD_GIT_TOKEN_FILE git password or token, read from a file
SWARMCLI_CD_GIT_TOKEN git password or token, given directly
SWARMCLI_CD_SERVER for the client commands: which controller to talk to

The controller refuses to start when no admin token is configured. An authorizer that merely rejected every request would be indistinguishable, from the outside, from a wrong token.

Chart compatibility

A chart may declare the engine it needs (swarmcliVersion: ">= 1.13.0" in Chart.yaml). The controller refuses to apply a plan containing a release this build's chart engine is too old for, and records why on the application's status — releases that would be unchanged are exempt, since applying will not touch them. There is no operator to ask, and the alternative is a failure minutes later inside the render, naming whatever feature happened to be missing.

The engine version is stamped into the image from the swarmcli release this module pins. A plain go build leaves it empty, and every compatibility check then reports Unknown rather than blocking.

Licence

Apache-2.0. See LICENSE.

Multi-swarm, projects/RBAC, SSO, notifications and managed secret rotation are planned as licensed capabilities in a separate private companion; everything in this repository — including the web UI — stays Apache-2.0.

Security

Please do not report vulnerabilities via public issues. See SECURITY.md.

Directories

Path Synopsis
Package api serves the controller's HTTP interface.
Package api serves the controller's HTTP interface.
Package application defines the Application spec, its observed status, and the JSON both serialise to.
Package application defines the Application spec, its observed status, and the JSON both serialise to.
Package appset sources the set of applications the controller reconciles, so that the applications file is itself GitOps-managed (issue #47).
Package appset sources the set of applications the controller reconciles, so that the applications file is itself GitOps-managed (issue #47).
Package authz answers who is calling the HTTP API and whether they may do what they are asking.
Package authz answers who is calling the HTTP API and whether they may do what they are asking.
Package backend applies compose-derived Swarm specs to a swarm through the moby client.
Package backend applies compose-derived Swarm specs to a swarm through the moby client.
Package capability names the optional interfaces a backend may implement.
Package capability names the optional interfaces a backend may implement.
Package client talks to the controller's HTTP API.
Package client talks to the controller's HTTP API.
cmd
swarmcli-cd command
Command swarmcli-cd is the SwarmCLI CD controller: it converges a Docker Swarm to the desired state declared in a Git repository, and serves the API that everything else observes it through.
Command swarmcli-cd is the SwarmCLI CD controller: it converges a Docker Swarm to the desired state declared in a Git repository, and serves the API that everything else observes it through.
Package compose turns a rendered compose manifest into the Swarm specs a stack is made of.
Package compose turns a rendered compose manifest into the Swarm specs a stack is made of.
Package config reads the applications the controller reconciles.
Package config reads the applications the controller reconciles.
Package controller is swarmcli-cd's entry point.
Package controller is swarmcli-cd's entry point.
Package drift decides whether an application matches git.
Package drift decides whether an application matches git.
Package extension is the fifth seam: it lets a companion module add HTTP routes to the controller's API without forking package api or package controller.
Package extension is the fifth seam: it lets a companion module add HTTP routes to the controller's API without forking package api or package controller.
Package git is the pull half of GitOps: it fetches an application's repository and pins it to a commit.
Package git is the pull half of GitOps: it fetches an application's repository and pins it to a commit.
Package health answers "is what is running actually working".
Package health answers "is what is running actually working".
Package notify carries reconcile events to whoever is listening.
Package notify carries reconcile events to whoever is listening.
Package prune deletes what git no longer declares: the resources of an application that has left the app set, and the rule deciding which services a release still in it has stopped declaring.
Package prune deletes what git no longer declares: the resources of an application that has left the app set, and the rule deciding which services a release still in it has stopped declaring.
Package reclaim deletes the on-disk caches an application leaves behind when it stops being reconciled: its clone under <data>/repos/<name> and its chart repository cache under <data>/charts/<name>.
Package reclaim deletes the on-disk caches an application leaves behind when it stops being reconciled: its clone under <data>/repos/<name> and its chart repository cache under <data>/charts/<name>.
Package reconcile is the pull loop: for each application, fetch the repository, render it, plan against the swarm, and — when the sync policy says so — apply.
Package reconcile is the pull loop: for each application, fetch the repository, render it, plan against the swarm, and — when the sync policy says so — apply.
Package regauth turns an application's registry credential into the encoded auth the moby client sends when it creates or updates a service.
Package regauth turns an application's registry credential into the encoded auth the moby client sends when it creates or updates a service.
Package seam holds the registration mechanism the open-core seams share.
Package seam holds the registration mechanism the open-core seams share.
Package secrets resolves secret material read from an application's source tree — a values file committed encrypted, for instance.
Package secrets resolves secret material read from an application's source tree — a values file committed encrypted, for instance.
Package source turns a checked-out working tree into what the chart engine's PlanApply takes: a release file, a chart source, and the reader it uses for values files.
Package source turns a checked-out working tree into what the chart engine's PlanApply takes: a release file, a chart source, and the reader it uses for values files.
Package swarms resolves an application's destination to something that can apply to it.
Package swarms resolves an application's destination to something that can apply to it.
local
Package local is the OSS swarms.Registry: it resolves exactly one swarm, the one this controller runs in, over the docker.sock it is mounted with (D2).
Package local is the OSS swarms.Registry: it resolves exactly one swarm, the one this controller runs in, over the docker.sock it is mounted with (D2).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL