Chapter 19Lesson 05~200 minutes

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.

Checkpoint labBuild onceDigest promotionRegistrySidecarTeardown

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 executors 0.
  • 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

  1. The sidecar/test containers and build network will be temporary Docker-host state; Jenkins build/source identity will remain controller/build state.
  2. The pushed image will survive test-container/workspace cleanup in the local registry under a content digest.
  3. 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.txt binds repository digest to source SHA and build URL.
  • promotion-record.txt references repository@digest and 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.log and sidecar/network IDs.
  • image-local-inspect.json.
  • push.log after review for secret-free output.
  • image-digest-evidence.txt.
  • promotion-record.txt.
  • cleanup.md and assumptions-limitations.md.

11. Cleanup and rollback

  1. Preserve non-secret evidence first.
  2. Remove remaining build-owned containers/networks by exact ID/name.
  3. Remove ch19-registry and /tmp/ch19-registry-lab only after digest verification.
  4. Remove only lab-specific images; do not globally prune a shared worker.
  5. Delete fake Jenkins credential ch19-registry-local if chapter-only.
  6. 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.

Next chapter

Chapter 20 — Kubernetes Plugin, Pod Templates, Dynamic Kubernetes Agents, Volumes, Service Accounts, and Cluster Scaling

Carry Docker image and trust discipline into dynamically provisioned Kubernetes agent pods.

Knowledge check

Answer before revealing the explanation.

1. What state changes should be predicted before the lab?

2. How do you prove promotion used the tested image?

3. Why are the registry credentials fake?

4. What evidence remains after local image-cache cleanup?

5. What carries into Chapter 20?

Official references and version notes

Assumption timestamp: 2026-09-17. Recheck Jenkins LTS/Java, Docker Pipeline health/version/dependencies, Docker Engine security/release notes, and all image tags/digests before repeating later.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.