
This guide deploys a working Pulp instance in a single container, configures the pulp CLI, opens the web interface, then exposes everything properly over HTTPS behind Traefik. Intended audience: administrators comfortable with podman or docker. Prerequisites: a Linux machine with podman (or Docker) and access to /dev/fuse. At the end you will have an artifact repository ready to receive your packages, local for testing, over HTTPS for production.
The official pulp/pulp image runs all of Pulp's services in a single container: the API, the content application, the workers, plus PostgreSQL, Redis and nginx. It is the recommended method for a small installation that does not need to scale across several machines.
What you will learn
- Run Pulp in a single container with
podman. - Set the administrator password and the
CONTENT_ORIGINsetting. - Install and configure the
pulpCLI. - Open the web interface served by the image.
- Expose the instance over HTTPS behind Traefik.
Prerequisites
- podman 4+ (or Docker) and the
/dev/fusedevice available (required by the container plugin). - A free HTTP port on the host (here 8080).
- About 2 GB of RAM free for PostgreSQL, Redis and the workers.
Run Pulp in a single container
Pulp needs persistent directories for the database, the artifact storage and its configuration. You create them next to the container before starting.
-
Prepare the directory tree and a minimal settings file:
Terminal window mkdir -p pulp/{settings,pulp_storage,pgsql,containers}cd pulpecho 'CONTENT_ORIGIN="http://localhost:8080"' > settings/settings.pyThe
CONTENT_ORIGINparameter is critical: it is the public URL at which your clients reach Pulp. All published metadata (PyPI index, container registry realm) uses it to build its links. If it does not match the port actually exposed,pip installandpodman pullfail with broken links. -
Start the container:
Terminal window podman run --detach --publish 8080:80 --name pulp \--volume "$(pwd)/settings":/etc/pulp \--volume "$(pwd)/pulp_storage":/var/lib/pulp \--volume "$(pwd)/pgsql":/var/lib/pgsql \--volume "$(pwd)/containers":/var/lib/containers \--device /dev/fuse \docker.io/pulp/pulp:3.114@sha256:a9e03d2bf3f40fea366f9f795a146ef91cabf6ea3d7b7ed8bc4b882f4353060cThe image is pulled by version tag and by digest, not by
latest. The tag tells the reader the version; the@sha256:pins byte for byte what is actually pulled, because a tag can be moved to other content without its name changing. Digest retrieved on 2026-10-21. -
Wait for initialisation (database migrations, ~1 minute), then check the API:
Terminal window curl -s http://localhost:8080/pulp/api/v3/status/ | headThe command must return a JSON listing the installed components (
core,file,python,container...). As long as you get a connection error, the container is still starting: it is not a failure.
Set the administrator password
The image sets no default password. You define it with the pulpcore-manager tool, run inside the container.
podman exec pulp bash -c 'pulpcore-manager reset-admin-password --password "VotreMotDePasseFort"'The output confirms Successfully set password for "admin" user. This admin account is the one you will use in the CLI and the web interface.
Install and configure the CLI
The pulp CLI is the most direct way to drive the instance. You install it in an isolated Python environment, so as not to pollute the system.
uv tool install "pulp-cli[pygments]"pipx install "pulp-cli[pygments]"You then record the instance's coordinates:
pulp config create \ --username admin \ --base-url http://localhost:8080 \ --password "VotreMotDePasseFort"Check that the CLI talks to the server:
pulp statusThe output shows the core version (3.114.0) and the list of active plugins. You now drive Pulp without touching curl.
Open the web interface
The image serves a graphical interface, Pulp UI, under the /ui/ path. Open in a browser:
http://localhost:8080/ui/Log in with the admin account and the password set above. Authentication goes through a session (sessionid and csrftoken cookies), like any ordinary web application.
Expose Pulp over HTTPS behind Traefik
The installation above, on localhost:8080, is only a local bootstrap to check that the instance starts. You never publish Pulp over HTTP for real clients: you place a reverse proxy that terminates TLS and forwards the traffic to the container. Traefik is a good fit thanks to its label-based configuration. On an isolated network, with no public authority, the same exposure works with a self-signed authority that you trust on the clients (see Pulp air-gapped).
Two rules govern correct operation behind a proxy:
CONTENT_ORIGINmust be the public HTTPS URL (for examplehttps://pulp.exemple.com). It feeds the links in the indexes and the container registry realm.- The proxy must forward the
Hostheader and setX-Forwarded-Proto: https, which Traefik does automatically. Without it, Pulp generateshttp://URLs and clients refuse the redirects.
-
Adjust the settings for the public URL, 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"The last four lines enable token authentication for the container registry; without a key pair consistent with the public URL,
podman pullreturns a 500 error that says nothing about its cause. -
Add the Traefik labels to the Pulp service. If you already have a Traefik listening on an HTTPS entrypoint (
websecure) with a certificate resolver (le), four labels on the Pulp container are enough, nothing else to declare:services:pulp:image: docker.io/pulp/pulp:3.114@sha256:a9e03d2bf3f40fea366f9f795a146ef91cabf6ea3d7b7ed8bc4b882f4353060cdevices:- "/dev/fuse"volumes:- "./settings:/etc/pulp"- "./pulp_storage:/var/lib/pulp"- "./pgsql:/var/lib/pgsql"- "./containers:/var/lib/containers"labels:- "traefik.enable=true"- "traefik.http.routers.pulp.rule=Host(`pulp.exemple.com`)"- "traefik.http.routers.pulp.tls.certresolver=le"- "traefik.http.services.pulp.loadbalancer.server.port=80"A single routing rule is enough: it covers the API (
/pulp/api/), the content (/pulp/content/), the registry (/v2/,/token/) and the interface (/ui/), all served by the same port 80 of the container. Thecertresolverterminates TLS and Traefik adds theHostandX-Forwarded-Protoheaders on its own. -
Start and verify:
Terminal window podman-compose up -d # or `docker compose up -d` depending on your enginecurl -s https://pulp.exemple.com/pulp/api/v3/status/ | headThe CLI and the clients now point to
https://pulp.exemple.com, andpodman login pulp.exemple.comworks without--tls-verify=false.
Air-gapped: a self-signed certificate instead of ACME
The le resolver above relies on Let's Encrypt, which requires Internet access and a public domain: unusable on an isolated network. Offline, you give Traefik a self-signed certificate instead of a certresolver. You declare it in a dynamic configuration file loaded by the file provider:
# certs.yaml (mounted and referenced via --providers.file.filename)tls: certificates: - certFile: /certs/pulp.crt keyFile: /certs/pulp.keyThe router label then simply becomes traefik.http.routers.pulp.tls=true (without certresolver). Generating the pulp.crt / pulp.key pair and, above all, trusting the authority on the clients are detailed in Pulp air-gapped, the logical next step of this guide.
And the container's internal nginx?
The all-in-one pulp/pulp image bundles its own nginx, which routes
requests to the API and the content application. In the setup above,
Traefik does not replace it: it sits in front, terminates TLS, and forwards
to port 80 of the container, where the internal nginx takes over. This double
proxy is normal and fully supported.
If you want to make Traefik the only web layer (no internal nginx), you
have to move from the single container to the multi-container deployment:
the pulp-minimal image provides pulp-api (port 24817) and
pulp-content (port 24816) separately, and the pulp-web image is
just the nginx. You then drop pulp-web and replicate its routing in Traefik:
| Path | Target service |
|---|---|
/pulp/content/ | pulp-content:24816 |
/v2/ (OCI registry) | pulp-content:24816 |
/pulp/api/v3/, /auth/, /token/, / | pulp-api:24817 |
It is more flexible but more delicate: the slightest forgotten route
(the container registry's /token/, for example) breaks a feature. For most
installations, keeping the internal nginx and putting Traefik in front
remains the safest choice.
Hands-on practice
The lab gives you depot.lab, a Debian 13 machine without Pulp. You install the instance there as a rootless container, set its configuration, and leave it running. The validation does not check that a container exists: it queries the status API, checks that the workers are seen as online, that the storage is reachable, and that a client on another machine reaches the repository. A final check reboots the host: a rootless instance without enable-linger does not come back, and the lab detects it.
Troubleshooting
| Symptom | Likely cause | Solution |
|---|---|---|
pip install downloads a file with the wrong digest | CONTENT_ORIGIN does not match the port or the public URL | Fix CONTENT_ORIGIN, restart the container |
podman pull returns 500 on /v2/ | TOKEN_SERVER or the ES256 key pair is missing | Add the token settings and generate the keys |
| The API takes a long time to answer on first start | PostgreSQL migrations in progress | Wait ~1 minute, watch podman logs pulp |
podman pull refuses access (denied) | Anonymous pull not allowed | podman login with the admin account |
Key points
- The
pulp/pulpimage runs the whole Pulp stack in a single container, ideal for getting started. - The admin password is set with
pulpcore-manager reset-admin-password. - The
CONTENT_ORIGINparameter must always reflect the real public URL, otherwise the published links are broken. - The
pulpCLI drives the instance; the/ui/interface explores it but remains young. - In production, a Traefik reverse proxy terminates TLS with a single routing rule to port 80 of the container.
Knowledge check
Eight questions on what makes an instance usable: what the all-in-one image carries, the public URL that builds every published link, and how to prove the service rather than the container.
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: Publish and consume artifacts with Pulp
A new full quiz with random questions
Work again only on the questions you missed