Pulp registry - fetch, upload, organize and distribute Software Packages

Pulp registry - fetch, upload, organize and distribute Software Packages

Do you like our work......we hire!

Never miss our publications about Open Source, big data and distributed systems, low frequency of one email every two months.

Following the recent Sonatype Nexus license change at the beginning of 2025, it has become preferable to turn toward an open-source and sovereign alternative without limitations. Pulp is a software package repository manager addressing this need. Versatile and modular, it is adapted to a wide range of formats such as npm, Maven, OCI and more.

This article will first focus on the advantages offered by Pulp in terms of security and data sovereignty, and then describe the steps to set up the software in a simple Kubernetes single-node environment.

The Advantages of Using Pulp

Over the last few years, a significant increase in “supply-chain” attacks has been observed, in which entire infrastructures were compromised due to malicious dependency packages. Using Pulp constitutes an important brick in supply chain security strategies by ensuring version control of packages and reducing risks of attacks based on external dependencies. This solution also offers data sovereignty by allowing complete control over the artifacts used in the infrastructure.

In an air-gapped environment, Pulp can also be configured to act as a proxy to secure access to external repositories while maintaining full isolation of the internal infrastructure. This approach allows storing required packages from external sources (such as official repositories) and serving them locally, thus ensuring dependency availability without requiring a direct Internet connection.

Installation and Usage of Pulp

Prerequisites

  • A Kubernetes cluster. The cluster here used is a single-node kind cluster.
  • The Helm package manager.
  • Docker CLI.

Installation

Pulp implements the Kubernetes operator pattern using CRDs and controllers to automatically manage the lifecycle of its components within the cluster.

# Add Pulp's repository to Helm
helm repo add pulp-operator https://github.com/pulp/pulp-k8s-resources/raw/main/helm-charts/ --force-update
# Deploy the operator
helm upgrade --install \
  -n pulp --create-namespace \
  --wait \
  pulp \
  pulp-operator/pulp-operator

Once the operator is deployed, the main Pulp instance is created using a CustomResource named “simple” taken from the official Pulp repository.

The tests were conducted on a single-node kind cluster, hence the file_storage_access_mode: "ReadWriteOnce" parameter. Note: in a multi-node environment, it’s crucial to change this parameter to ReadWriteMany.

cat <<'YAML' | kubectl apply -n pulp -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: pulp-custom-settings
data:
  token_server: '"http://localhost:10000/token/"'
  content_origin: '"http://localhost:10000"'
  ansible_api_hostname: '"http://localhost:10000"'
  pypi_api_hostname: '"http://localhost:10000"'
---
apiVersion: repo-manager.pulpproject.org/v1
kind: Pulp
metadata:
  name: example-pulp
spec:
  api: replicas: 1
  content: replicas: 1
  worker: replicas: 1
  web: replicas: 1

  database:
    postgres_image: postgres:15
    postgres_storage_class: standard

  # The "ReadWriteOnce" parameter must be changed to "ReadWriteMany" in a multi-node environment
  file_storage_access_mode: "ReadWriteOnce"
  file_storage_size: "2Gi"
  file_storage_storage_class: standard

  cache:
    enabled: true
    redis_storage_class: standard

  ingress_type: nodeport
  nodeport_port: 30000

  custom_pulp_settings: pulp-custom-settings
YAML

After a few minutes, we check that the installation has completed.

kubectl -n pulp get pods -w
#> NAME                                                READY   STATUS      RESTARTS   AGE
#> example-pulp-api-77478484b4-jz2l9                   1/1     Running     0          4m3s
#> example-pulp-content-7dc7895cc-2pjqf                1/1     Running     0          4m3s
#> example-pulp-database-0                             1/1     Running     0          4m4s
#> example-pulp-pulpcore-migration-8f7lt-kwcdm         0/1     Completed   0          4m3s
#> example-pulp-redis-d989ffd4c-h6nxx                  1/1     Running     0          4m4s
#> example-pulp-reset-admin-password-cj825-fwxdq       0/1     Completed   0          4m3s
#> example-pulp-web-75ddc6644c-lzf5j                   1/1     Running     0          4m2s
#> example-pulp-worker-6c99876989-sb267                1/1     Running     0          4m3s
#> pulp-operator-controller-manager-579bfc5bdb-8lpr6   1/1     Running     0          5m24s

We can now query the API.

kubectl -n pulp port-forward svc/example-pulp-web-svc 10000:24880
curl -s http://localhost:10000/pulp/api/v3/status/ | jq
#> {
#>   "versions": [
#>     {
#>       "component": "core",
#>       "version": "3.114.2",
#>       "package": "pulpcore",
#>       "module": "pulpcore.app",
#>       "domain_compatible": true
#>     },
#> ...

Example Usage: Pulp in pull-through Mode

In this example, Pulp is configured in pull-through mode. It acts as a proxy to another remote registry, particularly useful for connecting air-gapped environments to external instances. Downloads are cached, ensuring resource availability even in case of Internet outages.

# Retrieve admin user credentials
PULP_ADMIN_USER="admin"
PULP_ADMIN_PASSWORD="$(
  kubectl -n pulp get secret example-pulp-admin-password \
  -o jsonpath='{.data.password}' | base64 -d
  )"
BASE_ADDR="http://localhost:10000"
# Create the pull-through remote
REMOTE_HREF=$(curl -k -u "$PULP_ADMIN_USER:$PULP_ADMIN_PASSWORD" \
  -X POST "${BASE_ADDR}/pulp/api/v3/remotes/container/pull-through/" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "quay-proxy",
    "url": "https://quay.io",
    "tls_validation": false
  }' | jq -r ".pulp_href")
# Create the pull-through distribution
curl -k -u "$PULP_ADMIN_USER:$PULP_ADMIN_PASSWORD" \
  -X POST "${BASE_ADDR}/pulp/api/v3/distributions/container/pull-through/" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "quay-proxy",
    "base_path": "quay-proxy",
    "remote": "'$REMOTE_HREF'"
  }'

Images can then be pulled through the Pulp registry.

docker login localhost:10000 -u "$PULP_ADMIN_USER" -p "$PULP_ADMIN_PASSWORD"
docker pull localhost:10000/quay-proxy/prometheus/busybox
#> Using default tag: latest
#> latest: Pulling from quay-proxy/prometheus/busybox
#> bb2e5dc6c183: Pull complete
#> c93f261857c3: Pull complete
#> Digest: sha256:065fa27af988710200f7a9ceb7a09ae2fa6604074898c650bd89459e36edd6cf
#> Status: Downloaded newer image for localhost:10000/quay-proxy/prometheus/busybox:latest
#> localhost:10000/quay-proxy/prometheus/busybox:latest

Going Further

Pulp offers a wide variety of additional features, such as support for many package formats (RPM, Maven, etc.) or repository versioning. Community UIs exist, such as pulp-ui or pulp-ui-2, although activity remains relatively low, with most use still relying on the REST API and CLI. Therefore, Pulp is, to date, one of the most credible alternatives to Sonatype Nexus.

Share this article

Canada - Morocco - France

We are a team of Open Source enthusiasts doing consulting in Big Data, Cloud, DevOps, Data Engineering, Data Science…

We provide our customers with accurate insights on how to leverage technologies to convert their use cases to projects in production, how to reduce their costs and increase the time to market.

If you enjoy reading our publications and have an interest in what we do, contact us and we will be thrilled to cooperate with you.

Support Ukrain