container#
The container plugin releases a container image to servers and VMs over SSH, with Docker or Podman. On each target it pulls the image, replaces the application's container and waits until it is ready. It uses the same SSH connection as remote-copy, so the targets need no agent.
The artifact is an image#
The artifact is an image in a registry, given as an oci:// source:
artifact:
type: container
name: shop
version: "1.4.0"
source: oci://registry.example.com/team/shop:1.4.0
The artifacts step does not download the layers: it reads the manifest from the registry and computes its digest, which becomes the artifact's sha256. From then on the plugin always starts registry.example.com/team/shop@sha256:<digest>, never the tag. This means:
- a deploy resumed after a crash, a promotion or a manual rollback release exactly the image that was verified;
- if someone moves the tag in the meantime, the
qacheck blocks the promotion:sha256 atteso <digest>, calcolato <other digest>; - in the descriptor you can pin
artifact.sha256to the digest (withoutsha256:) or useoci://registry/team/shop@sha256:<digest>directly.
OCI and Docker v2 manifests are accepted, multi-architecture indexes included. Registries that require a token (Docker Hub, GHCR, Harbor, GitLab) are handled automatically. Credentials are configured as for the other artifact sources:
artifacts:
allow_unlisted: false
sources:
- prefix: oci://registry.example.com/team/
auth: {type: basic, username: manent, secret_ref: "vault:secret/data/manent/registry#password"}
oci+http:// is a plain-HTTP registry, for development and labs only.
Configuration#
plugins:
container:
known_hosts: ~/.ssh/known_hosts # default
insecure_ignore_host_key: false # true only in development
artifact_types: [container] # default
Deploy parameters#
| Parameter | Required | Description |
|---|---|---|
ssh_key_secret |
one of the two | Name of the secret with the PEM private key. |
password_secret |
one of the two | Name of the secret with the SSH password. |
runtime |
no | docker (default) or podman. |
sudo |
no | true to run the runtime with sudo -n. Normally the user is in the docker group. |
container_name |
no | Name of the container. Default: the application name. |
run_args |
no | Extra run arguments: ports, volumes, network, --env-file, limits. For example -p 8080:8080 --env-file /etc/shop.env. |
command |
no | Arguments passed to the container after the image. |
registry_username |
no | User for the registry login on the target. |
registry_password_secret |
with registry_username |
Secret with the registry password. It is passed on the stdin of login, never on the command line. |
wait_timeout |
no | How long to wait for startup (default 60s). |
stable_for |
no | Without a HEALTHCHECK in the image: how long the container must run without restarts to be considered ready (default 5s). |
artifact.version is required and becomes the container's manent.version label. Every target must have a user.
run_args and command are commands
They reach the target's shell as written. Define them in the environment configuration and exclude them from the descriptor_policy, like the remote-copy commands.
plugin: container
targets:
- {host: vm1.example.com, user: deploy, labels: {region: eu-1, az: eu-1a}}
- {host: vm2.example.com, user: deploy, labels: {region: eu-1, az: eu-1b}}
params:
ssh_key_secret: deploy-key
run_args: "-p 8080:8080 --env-file /etc/shop/prod.env"
wait_timeout: 90s
secrets:
- {name: deploy-key, ref: "vault:secret/data/shop/prod#ssh_key"}
rollback:
enabled: true
How it works#
For each target (one after the other, or parallel at a time):
- it reads the running container (
container inspect): version, image and state; - if the container already has the same image and version and is ready, it leaves it alone. Retries and recovery do not restart the application;
- it logs in to the registry, if configured;
- it pulls the image by digest;
- it removes the previous container and starts the new one with
--restart unless-stopped, the labelsmanent.application,manent.environment,manent.versionandmanent.deployment,run_argsandcommand; - it waits until the container is ready: with a
HEALTHCHECKin the image, until it ishealthy; without one, until it has been running forstable_forwithout restarts. A container that exits or isunhealthyfails the deploy, and the last lines of its log go to the deploy log.
After the first target that fails no other target starts; the ones already running finish. Between removing the old container and starting the new one the application on that target does not answer: to avoid downtime, use a wave rollout with maxUnavailable and a load balancer that skips targets that are not ready.
Rollback#
- Live release (
pluginlocator): version and image read from the container's labels. Targets without a container are ignored; different releases across targets stop the deploy at the checkpoint. - Rollback: on each target it restarts the container with the previous image, by digest, with the same wait as the deploy. It carries on if a target fails. Previous images stay cached on the target, so the rollback does not depend on the registry; if they are missing they are pulled again.
The container artifact type allows rollback (rollback.artifact_types.container.allowed: true in the defaults).
Dry-run#
In dry-run, for each target: SSH connection, runtime version with the user's permissions, and the running container. It does not pull and does not change any container.
Result#
{
"plugin": "container",
"targets": [
{
"host": "vm1.example.com",
"status": "deployed",
"details": {
"container": "shop",
"image": "registry.example.com/team/shop@sha256:4f1c…",
"previous_version": "1.3.0",
"previous_image": "registry.example.com/team/shop@sha256:9ab2…",
"state": "running healthy"
}
}
]
}