
This guide turns Pulp into a container image registry: you cache Docker Hub through a pull-through, pull an image through Pulp with podman, then scan it with Trivy. Intended audience: teams that want a private OCI registry without depending on Docker Hub's rate limiting, and want to add a security check. Prerequisites: a Pulp instance with token authentication configured (see Install Pulp), podman and trivy on the client machine.
The pulp_container plugin turns Pulp into an OCI/Docker-compatible registry. It can host your own images, but also act as a transparent cache (pull-through) for an upstream registry: the first time a client asks for an image, Pulp fetches it from upstream, stores it, and serves it directly on subsequent calls.
What you will learn
- Configure a pull-through cache to Docker Hub.
- Pull an image through Pulp with
podman. - Scan the images served by Pulp with Trivy.
- Understand why this check belongs in the CI.
Prerequisite: token authentication
Pulp's container registry requires token authentication, the standard mechanism of Docker registries. It relies on an ES256 key pair and on the TOKEN_SERVER setting, to be defined in settings/settings.py:
CONTENT_ORIGIN = "https://pulp.exemple.com"TOKEN_SERVER = "https://pulp.exemple.com/token/"TOKEN_SIGNATURE_ALGORITHM = "ES256"PUBLIC_KEY_PATH = "/etc/pulp/certs/token_public_key.pem"PRIVATE_KEY_PATH = "/etc/pulp/certs/token_private_key.pem"You generate the key pair in the certificates directory mounted by the container. This directory does not exist yet: the tree created at installation only holds settings, pulp_storage, pgsql and containers. Without the mkdir, both openssl commands fail on a path that cannot be found.
mkdir -p settings/certsopenssl ecparam -genkey -name prime256v1 -noout -out settings/certs/token_private_key.pemopenssl ec -in settings/certs/token_private_key.pem -pubout -out settings/certs/token_public_key.pempodman stop pulpuntil ! ss -ltn | grep -q ':8080 '; do sleep 2; donepodman start pulpYou stop then start, instead of podman restart: on a rootless instance the restart fails with Failed to bind port 8080 and leaves the container down, because the port has not been handed back to the system yet. This is measured in Backing up and restoring Pulp.
After the restart, a call to https://pulp.exemple.com/v2/ must return 401 and not 500: that is the sign that token authentication is active and correctly configured.
Configure the pull-through cache to Docker Hub
The pull-through cache associates a remote (the upstream registry) with a distribution (the local URL). Today these objects are created through the REST API: the CLI does not expose them yet.
-
Create the pull-through remote to the Docker Hub registry:
Terminal window curl -u admin:MOT_DE_PASSE \-X POST https://pulp.exemple.com/pulp/api/v3/remotes/container/pull-through/ \-H 'Content-Type: application/json' \-d '{"name":"dockerhub","url":"https://registry-1.docker.io"}' -
Get the identifier of the remote just created:
Terminal window RH=$(curl -s -u admin:MOT_DE_PASSE \"https://pulp.exemple.com/pulp/api/v3/remotes/container/pull-through/?name=dockerhub" \| python3 -c "import sys,json;print(json.load(sys.stdin)['results'][0]['pulp_href'])") -
Create the distribution that exposes this cache under the
dockerhubpath:Terminal window curl -u admin:MOT_DE_PASSE \-X POST https://pulp.exemple.com/pulp/api/v3/distributions/container/pull-through/ \-H 'Content-Type: application/json' \-d "{\"name\":\"dockerhub\",\"base_path\":\"dockerhub\",\"remote\":\"$RH\"}"
Pull an image through Pulp
The cache is ready. You authenticate against the Pulp registry, then pull an image: Pulp fetches it from Docker Hub on the fly, puts it in cache and serves it. The password is not given as an argument: podman login asks for it, so it stays neither in the shell history nor in the process list. The curl calls above, on the other hand, expose it both ways; they are written like that to stay readable, but outside a demonstration you go through a .netrc file or a variable read from a secrets manager.
podman login -u admin pulp.exemple.compodman pull pulp.exemple.com/dockerhub/library/alpine:3.22Pulp automatically creates a dockerhub/library/alpine distribution for the requested image. Subsequent downloads are served from the cache, without touching Docker Hub again, which gets around rate limiting and speeds up builds.
Scan images with Trivy
Hosting images is not enough: you have to check their vulnerabilities. This is where the security control that Pulp does not provide natively is built. Trivy scans an image directly at the address served by Pulp:
trivy image --scanners vuln --severity HIGH,CRITICAL \ pulp.exemple.com/dockerhub/library/alpine:3.22Trivy pulls the image through Pulp, analyses its packages and lists the CVEs. An image with no package manager (such as busybox) returns nothing to analyse; an image based on a distribution (Alpine, Debian) reveals its real state.
Put the scan in the CI, not in Pulp
The right place for this check is the CI/CD, not the registry. The robust pattern is as follows:
- The pipeline pulls the candidate image from Pulp.
- Trivy scans it; the job fails if a critical CVE is present (
trivy image --exit-code 1 --severity CRITICAL). - Only a validated image is promoted to a production repository in Pulp.
This way, Pulp stores and distributes, while Trivy decides what gets through. This separation of roles reproduces, with free building blocks, what Nexus Firewall or Artifactory Xray bill as a built-in feature.
Hands-on practice
The lab has you serve images through Pulp, with its token authentication and its pull-through cache. The validation is not satisfied with a podman pull that succeeds: it first removes the image from the local cache, otherwise podman would return without asking the repository for anything. It also checks that the pull-through objects exist on their own API endpoints, which the CLI does not expose.
The four labs of this module have now been played. The level exam measures what remains when the step-by-step instructions disappear: an instance inherited as is, whose builds all go out to the Internet, whose in-house packages travel by email and whose images come straight from Docker Hub. Seven graded tasks, 100 points, 70 to pass, everything graded on the real state of the instance and checked from a client that is not the repository. No hint is provided.
Key points
- The
pulp_containerplugin makes Pulp an OCI registry able to host images and cache Docker Hub. - The registry requires token authentication (ES256 keys +
TOKEN_SERVER); a 500 error on/v2/signals missing settings. - The pull-through cache is created through the REST API and gets around Docker Hub's rate limiting.
- Trivy scans the images served by Pulp; the check belongs in the CI, where it can block a vulnerable image.
- Pulp stores, Trivy decides: two free building blocks that replace a paid curation feature.
Knowledge check
Eight questions on turning Pulp into an OCI registry: token authentication, the pull-through cache, and where the vulnerability scan really belongs.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Securing Pulp: RBAC, content guards and signing
A new full quiz with random questions
Work again only on the questions you missed