Build and move container images
Money first. An image registry bills for what it stores, and nothing here ever deletes an image. Every push you make stays until you remove it with the image registry's own tools. Pushing the same image twice costs nothing extra — its layers are already there — but a new build that changes one byte is a new image.
OctoMY™ builds, inspects, pushes, pulls and copies
OCI container images itself. There is no Docker installation, no daemon and no
docker command involved: the image format and the image registry protocol are
implemented directly, in the same C++ as the rest of the tree.
What you need
-
A build of
fleet-ctl:qbs build -p fleet-ctlIt lands in
default/install-root/usr/local/bin/. -
For a push or a copy, an image registry you can write to, reachable over HTTPS. Plain HTTP is accepted only for an image registry on this machine (
localhost,127.0.0.1,::1), and no option changes that.
Build an image
Put the files the image should hold in a directory, laid out as they should appear inside the image, then build:
fleet-ctl image build \
--layout ~/images --ref agent \
--root /tmp/agent-root \
--platform linux/arm64 \
--entrypoint /usr/local/bin/agent
This writes an OCI image layout to ~/images and names the image agent
inside it. The digest of the new image is printed on standard output, and
nothing else is, so a script can capture it.
Builds are reproducible. The same files give the same digest on any machine on any day: every file is stored with a modification time of zero, owner 0:0, names in a fixed order, and only its executable bit kept from its permissions. That is what lets a second push skip every layer the image registry already holds.
A layer refuses a few things by name rather than storing them wrongly: hard links, sparse files, device nodes, named pipes and sockets. If a build stops on one of them, the message names the file.
To build on top of another image, point at the layout that holds it:
fleet-ctl image build --layout ~/images --ref agent --root /tmp/agent-root \
--platform linux/arm64 --base-layout ~/base-images --base-ref runtime
A base can come from a public image registry. Pull it into a layout first:
fleet-ctl image pull docker.io/library/debian:trixie-slim --layout ~/base-images --ref debian
A public image is usually an index, with one image for each platform. The build
takes the one for its --platform, and stops with the list of platforms the
index holds when none matches. A container has no UTF-8 locale unless you give
it one, so add --env LANG=C.UTF-8 for a Qt program.
Look inside an image
fleet-ctl image inspect --layout ~/images --ref agent
fleet-ctl image inspect registry.example.com/team/agent:v1
Both print the same JSON: the manifest digest, the platforms, the config, and
every layer twice — once as layers, the digests of the compressed bytes
the image registry stores, and once as diffIds, the digests of the
uncompressed content. When a pull fails with a digest mismatch, the pair is the
first thing to compare.
Push, pull and copy
fleet-ctl image push --layout ~/images --ref agent registry.example.com/team/agent:v1
fleet-ctl image pull registry.example.com/team/agent:v1 --layout ~/mirror --ref agent
fleet-ctl image copy registry.example.com/team/agent:v1 eu.registry.example.com/team/agent:v1
A push checks each blob before it sends anything and skips the ones the image registry already has. Large layers go up in chunks, and a dropped connection resumes from where the image registry says it stopped instead of starting over. A push refuses to finish any blob whose bytes do not match its digest.
A copy keeps the source's digest exactly. Within one image registry it moves layers between repositories without transferring them; between two image registries it fetches only what the target is missing.
Every pull is verified. A layer, config or manifest whose bytes do not hash to the digest that named it is rejected and never written.
Credentials
For an image registry that needs a login, give the username and feed the password on standard input, from wherever you keep it:
fleet-ctl image push --username robot --password-stdin \
--layout ~/images --ref agent registry.example.com/team/agent:v1 < ~/.secrets/registry-password
The password is used for that one command and stored nowhere. fleet-ctl reads
no environment variable and no Docker configuration file for credentials.
Measure an image registry with small objects
fleet-ctl image bench registry.example.com/team/bench --count 10000 --bytes 4096 --compare-dir /tmp/objects
The bench pushes each object as one artifact, a blob and a manifest tagged with
its name, and then writes the same objects as files in --compare-dir. It prints
both wall times as JSON. Use it before you put many small files, such as a
compile cache, in an image registry.
From OPAL
The same four operations are OPAL tasks, so a plan, a trigger or the assistant can run them:
| Task | Does | Tier |
|---|---|---|
oci.inspect |
Read an image's manifest, config and layer digests | operational |
oci.build |
Build an image from a directory into a layout. No network | operational |
oci.push |
Push an image from a layout to an image registry | secured |
oci.copy |
Copy an image between image registries | secured |
oci.push and oci.copy use the image registry credentials stored for this
node, sealed so that only this node can open them.