
This guide backs up and restores a Pulp instance without losing anything, with podman containers as well as on Kubernetes, and also shows how to export the content of a single repository to serve it again elsewhere. Intended audience: administrators responsible for the continuity of an artifact repository. Prerequisites: a running Pulp instance. The point to understand before anything else: with Pulp, data is spread across three distinct locations, and backing up one without the others gives a broken restore.
Many people think that "everything is in the database". That is wrong, and the mistake is costly on restore day. Understanding where the data lives is the prerequisite for any reliable backup strategy.
What you will learn
- Identify the three data locations of Pulp.
- Back up a container instance (database, artifacts, keys).
- Restore in the right order to avoid dead links.
- Automate backup and restore on Kubernetes.
- Export the content of a repository, and import it on another instance.
Where the data lives: three locations, not one
Pulp does not store everything in the same place. A complete backup must cover all three:
| Location | Content | Without it, at restore time |
|---|---|---|
| PostgreSQL | Metadata: repositories, versions, publications, distributions, tasks, RBAC | No structure left: the artifacts exist but nothing knows how to serve them |
File storage (/var/lib/pulp/media, or S3 object storage) | The artifacts themselves (binaries, addressed by sha256) | The database points to missing files: dead links |
Encryption keys (/etc/pulp/certs) | The key that encrypts sensitive fields (remote credentials) and the secret key | The encrypted credentials are unrecoverable |
The database therefore holds metadata only. The binaries live separately, on a file system or in object storage. Redis only serves as an ephemeral task queue and does not need to be backed up.
Backing up a container instance
In a podman deployment, the three locations are mounted volumes. The backup consists of freezing the database and archiving the volumes.
-
Dump the PostgreSQL database. The text dump is portable:
Terminal window podman exec pulp bash -c 'pg_dump -U pulp pulp' > pulp-db.sqlThe output should produce a file of several thousand lines starting with
-- PostgreSQL database dump. -
Archive the artifacts from the storage volume:
Terminal window podman exec pulp tar czf - -C /var/lib/pulp media > pulp-artifacts.tar.gz -
Copy the encryption and session keys:
Terminal window podman exec pulp tar czf - -C /etc/pulp certs > pulp-certs.tar.gz
These three files (pulp-db.sql, pulp-artifacts.tar.gz, pulp-certs.tar.gz) form a complete and consistent backup. Store them together, encrypted, off the Pulp machine.
Restoring a container instance, in the right order
The restore order is not arbitrary. You restore the artifacts and the keys first, and the database last.
-
Restore the keys and the artifacts into the volumes of the new instance:
Terminal window tar xzf pulp-certs.tar.gz -C /chemin/volume/etc-pulptar xzf pulp-artifacts.tar.gz -C /chemin/volume/var-lib-pulp -
Reload the database:
Terminal window podman exec -i pulp bash -c 'psql -U pulp pulp' < pulp-db.sql -
Check that the API responds and that the repositories are there:
Terminal window pulp statuspulp file repository list
The logic is simple: a restored database that points to a missing artifact breaks the service, whereas an artifact present without a database record is simply orphaned, hence harmless and cleaned up later by the orphan cleanup. So you put the immutable data first.
On Kubernetes: PulpBackup and PulpRestore
The operator automates the whole cycle through two custom resources. You describe a backup, and the operator captures the database, artifacts and secrets to a dedicated PVC; a restore resource recreates the complete instance from that PVC.
Backing up with PulpBackup
-
Create a destination PVC for the backup:
apiVersion: v1kind: PersistentVolumeClaimmetadata:name: pulp-backup-claimnamespace: pulpspec:accessModes: ["ReadWriteOnce"]storageClassName: local-pathresources:requests:storage: 5Gi -
Declare the backup with a
PulpBackupresource:apiVersion: repo-manager.pulpproject.org/v1kind: PulpBackupmetadata:name: sauvegarde-pulpnamespace: pulpspec:deployment_name: pulpbackup_pvc: pulp-backup-claimTerminal window kubectl apply -f pulpbackup.yamlThe operator launches a job which, once finished, sets the
BackupCompletecondition toTrue. The PVC then contains a timestamped folder with thepulp.dbdump, thecr_object(the instance definition) and all the secrets, includingdb_fields_encryption_secret.yaml(the encryption key) andpulp_secret_key.yaml.
Restoring with PulpRestore
The restore is declared just as declaratively. It recreates the complete instance and reloads the database and secrets from the backup PVC.
apiVersion: repo-manager.pulpproject.org/v1kind: PulpRestoremetadata: name: restauration-pulp namespace: pulpspec: deployment_name: pulp backup_name: sauvegarde-pulp backup_pvc: pulp-backup-claimkubectl apply -f pulprestore.yamlThe backup_name field is mandatory: it designates the PulpBackup resource to restore. The operator then recreates the database, the API, the content, the workers and the reverse proxy, then reloads the dump and the secrets. Next, check that your repositories are back:
kubectl -n pulp port-forward svc/pulp-api-svc 24817:24817 &curl -s -u admin:MOT_DE_PASSE \ http://localhost:24817/pulp/api/v3/repositories/file/file/Backing up the content of a repository: export and import
The two previous methods back up the machine. Pulp can also back up the content of a repository, independently of the instance that hosts it: this is export, and it is what you need when the goal is to serve again elsewhere rather than to rebuild the same instance.
The two levels do not replace each other. An export contains neither the accounts, nor the remotes, nor the encryption key: it contains artifacts and the metadata that describe them. It is restored on a new instance, or alongside, in another repository.
Allowing the paths, and restarting only once
Pulp only exports to a directory listed in ALLOWED_EXPORT_PATHS,
and only imports from a directory listed in ALLOWED_IMPORT_PATHS.
Without these two settings, creating the exporter fails with a
message that leaves no doubt:
Error: {"path":["Path '/var/lib/pulp/exports' is not an allowed export path"]}These settings are read at startup only. So set both at once, then restart a single time:
ALLOWED_EXPORT_PATHS = ["/var/lib/pulp/exports"]ALLOWED_IMPORT_PATHS = ["/var/lib/pulp/imports"]Exporting, then getting the file off the machine
The exporter is an object that you create once; the export is an
operation that you rerun each time. The two have distinct
subcommands, and this is the most frequent trap: pulp exporter for the object, pulp export for the operation, which expects
--exporter and not --name.
pulp exporter pulp create --name secours \ --path /var/lib/pulp/exports \ --repository python:python:interne
pulp export pulp run --exporter secoursThe operation produces two inseparable files: a .tar archive and
its -toc.json table of contents. One without the other restores nothing.
And above all, get them off the machine. An export left in
/var/lib/pulp/exports disappears with the disk it was meant to protect.
This is the most widespread and the most costly mistake: you discover on the day of
the outage that the backup was inside the outage.
Importing: the operation the CLI cannot launch
Verified on pulp-cli 0.41.1: pulp importer pulp only exposes create,
destroy, list, show and update, and pulp import does not exist. The
Pulp documentation only shows the operation through the API, so that is the only
way.
Three points to know before you start:
- the destination repository must exist and be empty; the import fills it, it does not create it;
- the
--repo-mapoption takes two arguments (source name, target name), not JSON; - the path of the
-toc.jsonis the one the container sees (/var/lib/pulp/imports/...), never the host's.
pulp python repository create --name restaurepulp importer pulp create --name secours --repo-map interne restaure
IMP=$(pulp --format json importer pulp show --name secours \ | python3 -c "import sys,json;print(json.load(sys.stdin)['pulp_href'])")
curl -u admin:MOT_DE_PASSE -X POST \ -H 'Content-Type: application/json' \ -d '{"toc": "/var/lib/pulp/imports/export-....-toc.json"}' \ "http://localhost:8080${IMP}imports/"The response returns a task_group and not a task: the import starts
several tasks, one per imported resource. Monitoring written for a
single task would wait forever for an end it will never see.
Once the import is finished, the restored repository must be published and distributed to serve: a filled repository with no distribution serves nobody.
Hands-on practice
The lab has you export a repository, then import it into an empty repository and serve a client with what was restored. Its central check is neither the export nor the import: it is that the backup sits on another machine. An export left in /var/lib/pulp/exports disappears with the disk it was meant to protect, and this is the most common outage. The lab also makes you run into the four refusals of Pulp before the first successful export, including the path seen from the container rather than from the host.
Key takeaways
- Pulp data lives in three places: database (metadata), file storage (artifacts), encryption keys.
- The database does not contain the artifacts: the binaries are on a file system or in object storage.
- The
database_fields.symmetric.keykey is essential: without it, the encrypted credentials of the remotes are lost. - In a container, you restore artifacts and keys first, database last (artifacts are immutable and addressed by digest).
- On Kubernetes,
PulpBackup/PulpRestoreautomate the full cycle; the database must run PostgreSQL 15 for the backup to work. - Export backs up the content of a repository, not the machine: it is restored on a new instance, but contains no accounts, no remotes and no keys.
- An export produces two inseparable files, the archive and its
-toc.json, and it is only worth anything once off the machine it protects. - Import can only be launched through the API: the CLI does not expose it, and the response returns a
task_group, not atask.
Knowledge check
Eight questions on what a backup must cover: the three places the data lives, the key that decides whether syncs survive, and the order that avoids dead links.
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: Deploying Pulp on Kubernetes with the operator
A new full quiz with random questions
Work again only on the questions you missed