Custom image & rollout
A Swarm service started without code creates its pool containers from a plain image: Ubuntu 26.04 for swarm@1, the Docker VM for swarm-vm@1. Usually you want more in there, like a runtime, your tools or your code. You get it by deploying to the Swarm service, the same way you deploy to any runtime service. Anything every pool container needs belongs in this image, not in snapshots you take afterwards.
Deploy to a Swarm service
Describe the image in a zerops.yaml and push it with zcli push, from the GUI, or through the GitHub or GitLab integration:
The build & deploy pipeline works as usual. The build runs, run.prepareCommands customize the runtime image, and the result becomes the image of the service. run.base can be any runtime Zerops supports. The service stays a Swarm service whatever you deploy to it.
Inside a pool container everything behaves like in a normal runtime service: your deployed files are in /var/www, run.envVariables and the service's other variables are set, run.initCommands run when the container starts, and run.ports are opened. If you define run.start, it runs in every pool container. If you do not, nothing is started, which is what you want when the containers only wait for your exec calls.
For swarm-vm@1 the base has to be a VM base, which today means docker@26.1. A container runtime cannot be deployed to a VM pool, and a VM base cannot be deployed to swarm@1. The deploy is refused with an error that names run.base.
What a deploy changes
This is where Swarm differs from a runtime service. A normal deploy replaces the running containers with new ones. In a Swarm pool the containers hold somebody's work, so a deploy never touches existing containers:
- Containers created after the deploy boot from the new image.
- Containers that already exist keep the image they were created with, together with their reservations, running commands and snapshots.
- A deploy does not create containers by itself. In a pool with a minimum of
0the first deploy leaves the pool empty.
The API tells you which containers are behind. Every container has an appVersionId, the deploy it was created for, and current, which is false when a newer deploy exists:
Acquire and run hand out outdated containers like any other. If your consumers must not land on an old image, roll the new one out right after the deploy. A fork runs the image of its source.
Roll out a new image
A rollout replaces every outdated container with a new one created from the current image. It works within the pool limits, in rounds: it removes as many outdated containers as the pool can lose without going below its minimum, then creates as many replacements as fit under its maximum, and repeats until all are replaced. A pool that sits at its minimum starts with the creates.
What happens to a container depends on its state:
| Outdated container | What the rollout does |
|---|---|
| Free | Removed and replaced during the call. |
| Reserved | Left alone and reported as skipped with held-by-other. With force=true and the admin token it is replaced like a free one. |
| Work in progress | Never interrupted. The container is marked, reported under retiring, and the orchestrator replaces it by itself once the command, restore, stop or start ends. No further call is needed. |
The response lists what happened: replaced (ids of the removed containers), created (the new containers), retiring, skipped with a reason for each, and createErrors for replacements that could not be created. The call returns when its own removals and creates are done, which takes minutes for a VM pool, and it continues if your client disconnects.
Two limits to know about:
- A pool whose minimum equals its maximum cannot be rolled. There is no room to remove a container first or to create one first, so the call is refused with
pool-fixed-size. Raise the maximum by one for the rollout. - A replacement is a new container with a new id and hostname. The old container's snapshots and everything on its disk are gone.