4. Images and the registry#
Note
This chapter is part of the reference documentation of the training-deployment-gitlabdeploy repository, and is maintained there.
The registry#
play4 runs zot, an OCI-native registry, behind its own Traefik, at
registry.playcluster.plone.org. Images live under the GitLab project's own
path:
registry.playcluster.plone.org/plone-training1/training-deployment-gitlabdeploy/backend
registry.playcluster.plone.org/plone-training1/training-deployment-gitlabdeploy/frontend
zot was chosen over Harbor, which needs nine services and 8 GB of RAM, and over
the reference registry:3, which has no per-repository access control. zot is
one container and does have it — which matters for the next section.
Two accounts, not one#
zot has two robot accounts with different rights:
Account |
Rights |
Used by |
|---|---|---|
push |
read and write |
the |
pull |
read only |
the swarm |
That split exists for a specific reason. The deploy runs:
docker stack deploy --with-registry-auth
--with-registry-auth copies the credentials the deploying client used onto
every swarm node, where they persist so nodes can re-pull after a reboot or a
rescheduled task. So whatever the deploy authenticates with ends up sitting on
the cluster indefinitely.
Important
Two consequences. The credential handed to the deploy must be read-only — otherwise every swarm node holds a push credential. And it must not expire, or a node reboot months from now fails to pull and the service will not start.
This is also why the deploy never uses $CI_JOB_TOKEN: it dies with the
pipeline.
Two registry modes#
The pipeline supports GitLab's own registry as well, switched by a single variable:
|
Result |
|---|---|
unset |
GitLab's registry via |
set |
that prefix, with |
The config job resolves it. It first expands any variable references in the
value itself — a group-level registry.example.org/$CI_PROJECT_PATH reaches
the job unexpanded — and stops if anything is left unresolved. It publishes the
result as IMAGE_PREFIX, not under the original name: a CI/CD variable outranks
a dotenv variable of the same name, so later jobs would get the raw value back.
It derives the host by taking everything before the first slash — which keeps
any :port:
echo "REGISTRY_HOST=${PREFIX%%/*}" >> build.env
Credentials are chosen by shell fallback at the point of use, never through the dotenv report:
- docker login
-u "${REGISTRY_USER:-$CI_REGISTRY_USER}"
-p "${REGISTRY_PASSWORD:-$CI_REGISTRY_PASSWORD}"
"${REGISTRY_HOST}"
Warning
Dotenv artifacts are not masked in job logs. Secrets must never pass through one. That is why credential selection happens in the shell.
Here, REGISTRY_IMAGE_PREFIX is set, to use the cluster's own registry. On
GitLab.com the built-in registry is always enabled, and that makes one mistake
quiet: if REGISTRY_IMAGE_PREFIX is missing from a pipeline — typically because
it was marked Protected and the pipeline runs on an unprotected branch — the
config job does not fail. It falls back to GitLab's registry, the build pushes
there, and the deploy looks for the images in zot. So REGISTRY_IMAGE_PREFIX is
the one variable that must not be protected.
Building#
docker:29-cli plus buildx, with the registry itself as the layer cache:
docker buildx build ${TAGS} \
--build-arg "${BUILD_ARG_NAME}=${BUILD_ARG_VALUE}" \
--build-arg "MAINTAINER=${MAINTAINER}" \
--cache-from "type=registry,ref=${IMAGE}/cache" \
--cache-to "type=registry,ref=${IMAGE}/cache,mode=max" \
--push "${IMAGE_ROLE}"
mode=max exports every intermediate layer, not just the final ones, so a
later build can resume from any step. The cache lives in the registry alongside
the images, at …/backend/cache.
Tagging, and why it matters#
TAGS="--tag ${IMAGE}:${IMAGE_TAG}"
if [ "${CI_COMMIT_BRANCH}" = "${CI_DEFAULT_BRANCH}" ]; then
TAGS="${TAGS} --tag ${IMAGE}:latest"
fi
Pipeline |
Tags |
|---|---|
push to |
|
git tag |
|
Every build gets an immutable tag. latest moves; sha-… never does. That
is what makes rollback possible — chapter 6 uses it.
Watching a build#
The log tells you which parts of this worked:
#7 importing cache manifest from .../backend/cache
#7 inferred cache manifest type: application/vnd.oci.image.manifest.v1+json
The cache was found and reused. On a first build it reports not found
instead, which is expected and harmless.
#20 pushing manifest for .../backend:sha-52d3e62e
#21 exporting cache to registry
Image pushed, then the cache exported for the next run.