Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Pulp as a container registry, with Trivy scanning

By updated

Improve this page

Pulp logo

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.

Terminal window
mkdir -p settings/certs
openssl ecparam -genkey -name prime256v1 -noout -out settings/certs/token_private_key.pem
openssl ec -in settings/certs/token_private_key.pem -pubout -out settings/certs/token_public_key.pem
podman stop pulp
until ! ss -ltn | grep -q ':8080 '; do sleep 2; done
podman start pulp

You 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.

  1. 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"}'
  2. 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'])")
  3. Create the distribution that exposes this cache under the dockerhub path:

    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.

Terminal window
podman login -u admin pulp.exemple.com
podman pull pulp.exemple.com/dockerhub/library/alpine:3.22

Pulp 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:

Terminal window
trivy image --scanners vuln --severity HIGH,CRITICAL \
pulp.exemple.com/dockerhub/library/alpine:3.22

Trivy 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:

  1. The pipeline pulls the candidate image from Pulp.
  2. Trivy scans it; the job fails if a critical CVE is present (trivy image --exit-code 1 --severity CRITICAL).
  3. 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_container plugin 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

8 questions
8 min
70% to pass

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

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn