Checkpoint Lab — Docker Pipeline, Containerized Build Steps, Docker Agents, Sidecars, Registries, and Image Workflows
Build once, test with a pinned container and sidecar, push to a disposable authenticated local registry, capture the digest, simulate promotion by that digest, remove transient Docker/workspace state, and prove retained evidence still maps to the original build and source.
Learning objectives
- Use a trusted Docker-capable agent and reviewed input image digests.
- Test synthetic source in a pinned container with a disposable sidecar/network.
- Build one candidate and push it to an authenticated loopback registry with fake credentials.
- Capture the registry digest and simulate promotion of that exact content without rebuilding.
- Remove build-owned Docker/registry/workspace state while retaining non-secret build/source/digest evidence.
1. Scenario
A release team wants Jenkins to create one container candidate, test it against a disposable service, and hand an immutable identity to later release automation. The checkpoint uses a local registry instead of a cloud service, but the production invariant is real: build once, identify by digest, promote that exact content.
2. Baseline and assumptions
-
Jenkins
2.568.3 LTS, Java 21, built-in node executors0. - Docker Pipeline
653.v2f2c08eff0ec. -
Dedicated trusted agent
ch19-docker, one executor. - Reviewed Docker Engine/CLI; record exact client/server versions and context.
-
Reviewed tags:
python:3.13-alpine3.24,alpine:3.24.1,registry:3.1.1,httpd:2.4.68-alpine3.24; resolve digests before run. -
Fake Jenkins credential
ch19-registry-local; no production/cloud/signing credentials.
3. Predict state changes first
- The sidecar/test containers and build network will be temporary Docker-host state; Jenkins build/source identity will remain controller/build state.
- The pushed image will survive test-container/workspace cleanup in the local registry under a content digest.
- Promotion simulation will reference the recorded digest and will not execute a second image build.
Write these predictions in predictions.md before
creating Docker resources.
4. Preflight evidence
set -eu
printf 'job=%s build=%s node=%s workspace=%s\n' \
"$JOB_NAME" "$BUILD_NUMBER" "$NODE_NAME" "$WORKSPACE"
docker version
docker context show
docker info --format 'server={{.ServerVersion}} driver={{.Driver}} security={{json .SecurityOptions}}'
docker ps --format '{{.ID}} {{.Names}} {{.Image}}'
docker network ls --format '{{.ID}} {{.Name}}'
Also record Jenkins/plugin inventory, item full name, build cause, and exact Git SHA. Verify the fake credential is available only in the intended lab/folder context.
5. Synthetic repository
app/message.txt
app/check.py
Dockerfile
Jenkinsfile
from pathlib import Path
value = Path('app/message.txt').read_text().strip()
assert value == 'hello-from-ch19'
print('source-check=ok')
FROM alpine:3.24.1
ARG SOURCE_SHA=unknown
ARG JENKINS_BUILD=unknown
LABEL org.opencontainers.image.revision=$SOURCE_SHA
LABEL io.jenkins.build=$JENKINS_BUILD
COPY app/message.txt /opt/ch19/message.txt
CMD ["cat", "/opt/ch19/message.txt"]
Record the base image’s resolved digest in evidence. For a stricter
production Dockerfile, pin the FROM image by digest.
6. Prepare the disposable registry
On the trusted Docker host, create
/tmp/ch19-registry-lab, generate a fake bcrypt htpasswd
entry for ch19user using
httpd:2.4.68-alpine3.24, and run
registry:3.1.1 bound only to
127.0.0.1:5000. Create the matching Jenkins
Username/Password credential ch19-registry-local. Do
not archive the htpasswd file.
7. Checkpoint Jenkinsfile
pipeline {
agent { label 'ch19-docker && trusted' }
options {
timeout(time: 20, unit: 'MINUTES')
timestamps()
disableConcurrentBuilds()
}
environment { REGISTRY = '127.0.0.1:5000' }
stages {
stage('Validate digest input') {
steps {
script {
if (!env.CH19_PYTHON_IMAGE?.startsWith('python@sha256:')) {
error('Set CH19_PYTHON_IMAGE to the reviewed python digest')
}
}
}
}
stage('Container + sidecar test') {
steps {
script {
def net = "ch19-${env.BUILD_TAG.replaceAll(/[^A-Za-z0-9_.-]/, '-')}"
sh "docker network create ${net}"
try {
docker.image(env.CH19_PYTHON_IMAGE).withRun(
"--network ${net} --network-alias api -v ${pwd()}/app:/srv:ro python -m http.server 8080 --directory /srv"
) { svc ->
echo "sidecar-id=${svc.id}"
retry(10) {
sleep 1
sh "docker run --rm --network ${net} ${env.CH19_PYTHON_IMAGE} python -c \"import urllib.request; assert urllib.request.urlopen('http://api:8080/message.txt', timeout=2).read().decode().strip() == 'hello-from-ch19'\""
}
sh "docker logs ${svc.id} > sidecar.log 2>&1 || true"
}
} finally {
sh "docker network rm ${net} || true"
}
}
}
}
stage('Build once') {
steps {
sh '''
set -eu
docker build \
--build-arg SOURCE_SHA="$GIT_COMMIT" \
--build-arg JENKINS_BUILD="$BUILD_URL" \
-t "ch19-demo:${BUILD_NUMBER}" .
docker image inspect "ch19-demo:${BUILD_NUMBER}" > image-local-inspect.json
'''
}
}
stage('Push + capture digest') {
steps {
withCredentials([usernamePassword(credentialsId: 'ch19-registry-local', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')]) {
sh '''
set -eu
REPO="$REGISTRY/ch19/demo"
REF="$REPO:${BUILD_NUMBER}"
printf '%s' "$REG_PASS" | docker login "$REGISTRY" --username "$REG_USER" --password-stdin >/dev/null
docker tag "ch19-demo:${BUILD_NUMBER}" "$REF"
docker push "$REF" | tee push.log
DIGEST=$(awk '/digest:/ {print $2}' push.log | tail -1)
test -n "$DIGEST"
printf 'repository=%s\ndigest=%s\nsource=%s\nbuild=%s\n' \
"$REPO" "$DIGEST" "$GIT_COMMIT" "$BUILD_URL" | tee image-digest-evidence.txt
docker pull "$REPO@$DIGEST"
docker logout "$REGISTRY" >/dev/null
'''
}
}
}
stage('Promotion simulation') {
steps {
sh '''
set -eu
. ./image-digest-evidence.txt
printf 'environment=staging-sim\nimage=%s@%s\nproducer=%s\n' \
"$repository" "$digest" "$build" | tee promotion-record.txt
# Deliberately no docker build here.
'''
}
}
}
post {
always {
archiveArtifacts artifacts: 'sidecar.log,image-local-inspect.json,push.log,image-digest-evidence.txt,promotion-record.txt', fingerprint: true, allowEmptyArchive: true
}
}
}
The Pipeline rejects an empty/mutable test-image value. Set
CH19_PYTHON_IMAGE from reviewed preflight evidence
before the run.
8. Expected observations
- Only the trusted Docker agent executes the Pipeline; the controller does not run build steps.
- Sidecar/test containers share one build-unique network, then disappear.
- The candidate image has Jenkins/source labels and is built exactly once.
- The local registry requires the fake credential and reports an image digest after push.
-
image-digest-evidence.txtbinds repository digest to source SHA and build URL. -
promotion-record.txtreferencesrepository@digestand contains no rebuild step.
9. Verification checklist
- Jenkins/Java/Docker Pipeline plugin versions recorded.
- Docker client/server/context/security mode recorded on the actual agent.
- Input tags and architecture-specific digests recorded.
- Item/build/source/cause/node/workspace evidence recorded.
- Sidecar/network IDs and readiness evidence captured.
- Candidate built exactly once.
- Registry push authenticated with fake credential ID; password absent from logs/artifacts.
- Registry-reported digest captured and exact digest pulled successfully.
- Promotion simulation references the same digest.
- Cleanup removes only lab/build-owned resources.
10. Required evidence packet
-
baseline.md: Jenkins/Java/plugin/Docker client+server/context. build-source.md: item/build URL/cause/Git SHA.agent.md: node/labels/workspace/trust class.-
input-images.txt: reviewed tags plus resolved digests. sidecar.logand sidecar/network IDs.image-local-inspect.json.push.logafter review for secret-free output.image-digest-evidence.txt.promotion-record.txt.-
cleanup.mdandassumptions-limitations.md.
11. Cleanup and rollback
- Preserve non-secret evidence first.
- Remove remaining build-owned containers/networks by exact ID/name.
-
Remove
ch19-registryand/tmp/ch19-registry-labonly after digest verification. - Remove only lab-specific images; do not globally prune a shared worker.
-
Delete fake Jenkins credential
ch19-registry-localif chapter-only. - Remove disposable SCM/agent resources if created solely for the lab.
12. Production operating model and bridge
Chapter 19 adds a precise Docker chain to the Jenkins operating model: source/build identity selects a trusted Docker-capable worker; the worker reaches a known engine/builder; test/build images are pinned by digest; workspaces and sidecars have explicit lifecycle; registry authentication is scoped; one candidate is built; the push returns an immutable digest; promotion uses that digest rather than rebuilding.
Chapter 20 changes the worker substrate to Kubernetes pod templates and dynamic agents. The same questions—image identity, trust, service account, workspace/volume, credentials, capacity, evidence, and teardown—remain.
Knowledge check
Answer before revealing the explanation.
1. What state changes should be predicted before the lab?
Predict temporary sidecar/network state, persistent registry digest state, and that workspace/container cleanup will not redefine the Jenkins build/source or pushed digest.
2. How do you prove promotion used the tested image?
The promotion record must reference the exact repository@sha256 digest captured from the original push; no rebuild occurs.
3. Why are the registry credentials fake?
The lab exercises real Jenkins binding/authentication without touching production accounts or repositories.
4. What evidence remains after local image-cache cleanup?
Jenkins build/source metadata and archived digest/promotion evidence remain; if the disposable registry remains, the digest can also be pulled from it.
5. What carries into Chapter 20?
Keep image digest, trust, credentials, workspace/volume, capacity, evidence, and teardown explicit while the worker lifecycle moves to Kubernetes pods.
Official references and version notes
-
Jenkins LTS changelog
— baseline
Jenkins 2.568.3 LTS, tested with Java 21 and 25; labs use Java 21 for Jenkins components. - Using Docker with Pipeline — Docker agents, workspace synchronization, multiple containers, sidecars, builds, remote servers, and custom registries.
-
Docker Pipeline plugin
— reviewed version
653.v2f2c08eff0ec, requires Jenkins 2.541.3, and is currently marked “up for adoption”. - Docker Pipeline steps — current step reference; deprecated Docker fingerprint steps are not used as authenticity/provenance evidence.
- Docker Engine security, protect daemon access, and rootless mode.
-
Docker Engine 29 release notes
— current release family at the chapter timestamp;
29.8.1was released 2026-09-15. -
Distribution Registry official image
— local lab baseline
registry:3.1.1. -
Python,
Alpine, and
httpd
official images — reviewed lab tags
python:3.13-alpine3.24,alpine:3.24.1, andhttpd:2.4.68-alpine3.24; resolve actual architecture-specific digests before execution.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.