Aller au contenu
Conteneurs & Orchestration medium

Distribution : déployer une registry privée on-premise (ex Docker Registry)

18 min de lecture

Vous voulez héberger vos images Docker en interne sans dépendre de Docker Hub ou Quay.io ? Distribution (anciennement Docker Registry v2) est le projet CNCF officiel pour créer une registry privée on-premise. Léger, stateless et compatible OCI, il se déploie en quelques minutes avec Docker Compose.

Ce guide couvre le déploiement complet d’une registry privée, de l’installation basique jusqu’à la configuration production avec TLS et authentification.

  • Déployer une registry avec Docker ou Docker Compose
  • Configurer TLS avec Let’s Encrypt ou certificats auto-signés
  • Ajouter l’authentification htpasswd ou token
  • Configurer le stockage : filesystem, S3, Azure, GCS
  • Exécuter le garbage collection pour libérer l’espace disque
  • Intégrer avec Kubernetes et vos pipelines CI/CD

Distribution est le projet open source officiel de la CNCF (Cloud Native Computing Foundation) pour implémenter une registry de conteneurs. C’est le même code qui propulse Docker Hub, GitHub Container Registry et de nombreuses registries cloud.

Le projet a connu plusieurs noms au fil du temps, ce qui peut prêter à confusion :

PériodeNomNotes
2013-2015Docker Registry v1Format propriétaire, abandonné
2015-2020Docker Registry v2Nouveau format, base du standard OCI
2020-présentCNCF DistributionProjet transféré à la CNCF, image registry:2
2024-présentDistribution 3.0Version majeure avec support OCI natif amélioré

Quand utiliser Distribution plutôt qu’une solution plus complète comme Harbor ?

CritèreDistributionHarborQuay (self-hosted)
ComplexitéTrès simpleMoyenneÉlevée
Ressources~50 MB RAM~2 GB RAM~4 GB RAM
Interface web❌ Non✅ Oui✅ Oui
Scan de sécurité❌ Non✅ Trivy/Clair✅ Clair
RBAC avancé❌ Non✅ Oui✅ Oui
Réplication❌ Non✅ Oui✅ Oui
Cas d’usageDev, CI/CD, petit clusterEntrepriseEntreprise Red Hat

En résumé : Distribution est idéal pour les environnements où vous avez besoin d’une registry simple, rapide à déployer, sans interface graphique ni fonctionnalités avancées. Pour la production enterprise, préférez Harbor.

Commençons par le déploiement le plus simple : une registry locale sans authentification ni TLS. Parfait pour le développement ou les tests.

Fenêtre de terminal
# Lancer une registry sur le port 5000
docker run -d \
--name registry \
--restart always \
-p 5000:5000 \
-v registry-data:/var/lib/registry \
registry:2
# Vérifier que la registry fonctionne
curl http://localhost:5000/v2/
# Résultat attendu : {}
Fenêtre de terminal
# Taguer une image existante pour la registry locale
docker pull alpine:3.19
docker tag alpine:3.19 localhost:5000/alpine:3.19
# Pousser vers la registry
docker push localhost:5000/alpine:3.19
# Vérifier la présence
curl http://localhost:5000/v2/_catalog
# {"repositories":["alpine"]}
# Supprimer l'image locale et la retirer
docker rmi localhost:5000/alpine:3.19
docker pull localhost:5000/alpine:3.19

Pour un déploiement plus robuste avec TLS et authentification, utilisez Docker Compose avec un fichier de configuration dédié.

registry/
├── docker-compose.yml
├── config.yml
├── auth/
│ └── htpasswd
└── certs/
├── domain.crt
└── domain.key

Créez config.yml avec les paramètres de votre registry :

version: 0.1
log:
level: info
formatter: text
fields:
service: registry
storage:
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: true
cache:
blobdescriptor: inmemory
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
tls:
certificate: /certs/domain.crt
key: /certs/domain.key
auth:
htpasswd:
realm: Registry Realm
path: /auth/htpasswd
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3

Créez docker-compose.yml :

services:
registry:
image: registry:2.8
container_name: registry
restart: always
ports:
- "5000:5000"
environment:
REGISTRY_STORAGE_DELETE_ENABLED: "true"
volumes:
- ./config.yml:/etc/docker/registry/config.yml:ro
- ./auth:/auth:ro
- ./certs:/certs:ro
- registry-data:/var/lib/registry
volumes:
registry-data:

Une registry de production doit être protégée par TLS. Docker refuse par défaut de communiquer avec une registry HTTP non sécurisée (sauf localhost).

La solution la plus simple pour obtenir un certificat valide automatiquement. Traefik gère le renouvellement et le reverse proxy.

services:
traefik:
image: traefik:v3.0
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
registry:
image: registry:2.8
labels:
- "traefik.enable=true"
- "traefik.http.routers.registry.rule=Host(`registry.example.com`)"
- "traefik.http.routers.registry.entrypoints=websecure"
- "traefik.http.routers.registry.tls.certresolver=letsencrypt"
volumes:
- registry-data:/var/lib/registry
volumes:
registry-data:
letsencrypt:

Pour les environnements internes sans accès Internet, générez un certificat auto-signé. Les clients devront faire confiance à ce certificat.

  1. Générer le certificat

    Fenêtre de terminal
    # Créer le répertoire des certificats
    mkdir -p certs
    # Générer une clé privée et un certificat (valide 365 jours)
    openssl req -x509 -nodes -days 365 -newkey rsa:4096 \
    -keyout certs/domain.key \
    -out certs/domain.crt \
    -subj "/CN=registry.local" \
    -addext "subjectAltName=DNS:registry.local,DNS:localhost,IP:192.168.1.100"
  2. Configurer Docker pour faire confiance au certificat

    Sur chaque machine cliente :

    Fenêtre de terminal
    # Créer le répertoire de certificats pour la registry
    sudo mkdir -p /etc/docker/certs.d/registry.local:5000
    # Copier le certificat
    sudo cp certs/domain.crt /etc/docker/certs.d/registry.local:5000/ca.crt
    # Redémarrer Docker
    sudo systemctl restart docker
  3. Tester la connexion

    Fenêtre de terminal
    docker login registry.local:5000
    # Username: admin
    # Password: ********
    # Login Succeeded

Distribution supporte plusieurs méthodes d’authentification. La plus simple est htpasswd, suffisante pour la plupart des cas d’usage.

L’authentification htpasswd utilise un fichier de mots de passe hashés, comme Apache. Simple à mettre en place, mais sans gestion fine des permissions (tout ou rien).

  1. Créer le fichier htpasswd

    Fenêtre de terminal
    # Créer le répertoire auth
    mkdir -p auth
    # Créer le premier utilisateur (bcrypt)
    docker run --rm --entrypoint htpasswd \
    httpd:2-alpine -Bbn admin motdepasse_securise > auth/htpasswd
    # Ajouter d'autres utilisateurs
    docker run --rm --entrypoint htpasswd \
    httpd:2-alpine -Bbn ci_user autre_motdepasse >> auth/htpasswd
  2. Configurer la registry

    Dans config.yml :

    auth:
    htpasswd:
    realm: Registry Realm
    path: /auth/htpasswd
  3. Tester l’authentification

    Fenêtre de terminal
    # Sans auth : erreur 401
    curl https://registry.local:5000/v2/
    # {"errors":[{"code":"UNAUTHORIZED",...}]}
    # Avec auth : succès
    curl -u admin:motdepasse_securise https://registry.local:5000/v2/
    # {}

Pour une gestion plus fine des permissions (lecture seule, écriture, par repository), utilisez un serveur d’authentification externe compatible avec le protocole Docker Registry Token.

Des projets comme Cesanta Docker Auth ou Portus implémentent ce protocole et permettent de définir des ACL granulaires.

Par défaut, Distribution stocke les images sur le filesystem local. Pour la production, vous pouvez utiliser un stockage objet (S3, GCS, Azure Blob).

Configuration simple pour un serveur unique. Le stockage est dans un volume Docker ou un répertoire local.

storage:
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: true

Pour la haute disponibilité et la scalabilité, utilisez un stockage objet S3. Compatible avec MinIO, Ceph, Wasabi et tout service S3-compatible.

storage:
s3:
accesskey: AKIAIOSFODNN7EXAMPLE
secretkey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
region: eu-west-1
bucket: my-registry-bucket
rootdirectory: /registry
encrypt: true
secure: true
v4auth: true
delete:
enabled: true
cache:
blobdescriptor: inmemory
storage:
azure:
accountname: myaccount
accountkey: ${AZURE_STORAGE_KEY}
container: registry
realm: core.windows.net
storage:
gcs:
bucket: my-registry-bucket
keyfile: /path/to/keyfile.json
rootdirectory: /registry

Les images Docker sont composées de layers (blobs) partagés entre images. Quand vous supprimez un tag, les blobs ne sont pas automatiquement supprimés — ils peuvent encore être référencés par d’autres images.

Le garbage collection (GC) identifie et supprime les blobs orphelins pour récupérer l’espace disque.

Avant le GC, vous devez supprimer les tags que vous ne voulez plus conserver.

Fenêtre de terminal
# Lister les tags d'une image
curl -u admin:password https://registry.local:5000/v2/myapp/tags/list
# Récupérer le digest d'un tag
DIGEST=$(curl -s -u admin:password \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
https://registry.local:5000/v2/myapp/manifests/v1.0.0 \
-I | grep -i docker-content-digest | awk '{print $2}' | tr -d '\r')
# Supprimer le manifest (soft delete)
curl -u admin:password -X DELETE \
https://registry.local:5000/v2/myapp/manifests/$DIGEST

Le GC doit être exécuté registry arrêtée ou en mode read-only pour éviter les corruptions.

Fenêtre de terminal
# Arrêter la registry
docker stop registry
# Dry-run : voir ce qui serait supprimé
docker run --rm \
-v registry-data:/var/lib/registry \
-v $(pwd)/config.yml:/etc/docker/registry/config.yml \
registry:2 garbage-collect --dry-run /etc/docker/registry/config.yml
# Exécution réelle
docker run --rm \
-v registry-data:/var/lib/registry \
-v $(pwd)/config.yml:/etc/docker/registry/config.yml \
registry:2 garbage-collect /etc/docker/registry/config.yml
# Redémarrer la registry
docker start registry

Pour utiliser votre registry privée avec Kubernetes, créez un Secret de type docker-registry et référencez-le dans vos Pods.

Fenêtre de terminal
kubectl create secret docker-registry registry-credentials \
--docker-server=registry.local:5000 \
--docker-username=admin \
--docker-password=motdepasse_securise \
--docker-email=admin@example.com \
-n default
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: app
image: registry.local:5000/myapp:v1.0.0
imagePullSecrets:
- name: registry-credentials

Si vous utilisez un certificat auto-signé, configurez containerd sur chaque nœud Kubernetes pour faire confiance à votre registry.

Fenêtre de terminal
# Créer le répertoire de configuration
sudo mkdir -p /etc/containerd/certs.d/registry.local:5000
# Créer hosts.toml
cat << 'EOF' | sudo tee /etc/containerd/certs.d/registry.local:5000/hosts.toml
server = "https://registry.local:5000"
[host."https://registry.local:5000"]
capabilities = ["pull", "resolve", "push"]
ca = "/etc/containerd/certs.d/registry.local:5000/ca.crt"
EOF
# Copier le certificat CA
sudo cp domain.crt /etc/containerd/certs.d/registry.local:5000/ca.crt
# Redémarrer containerd
sudo systemctl restart containerd

Voici un workflow pour pousser vos images vers votre registry privée depuis GitHub Actions.

Créez des secrets pour l’authentification à votre registry privée :

SecretValeur
REGISTRY_URLregistry.example.com:5000
REGISTRY_USERNAMEci_user
REGISTRY_PASSWORDMot de passe htpasswd
name: Build and Push to Private Registry
on:
push:
branches: [main]
tags: ['v*']
env:
IMAGE_NAME: myapp
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
- name: Login to Private Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ${{ secrets.REGISTRY_URL }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: ${{ secrets.REGISTRY_URL }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha,prefix=
- name: Build and push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

Distribution expose une API REST standard (OCI Distribution Spec) pour interagir programmatiquement avec la registry.

EndpointMéthodeDescription
/v2/GETVérifier que la registry fonctionne
/v2/_catalogGETLister tous les repositories
/v2/{name}/tags/listGETLister les tags d’une image
/v2/{name}/manifests/{ref}GETRécupérer un manifest
/v2/{name}/manifests/{ref}DELETESupprimer un manifest
/v2/{name}/blobs/{digest}GETTélécharger un blob
Fenêtre de terminal
# Lister tous les repositories
curl -u admin:password https://registry.local:5000/v2/_catalog
# {"repositories":["alpine","myapp","nginx"]}
# Lister les tags d'une image
curl -u admin:password https://registry.local:5000/v2/myapp/tags/list
# {"name":"myapp","tags":["v1.0.0","v1.1.0","latest"]}
# Récupérer le manifest d'un tag
curl -u admin:password \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
https://registry.local:5000/v2/myapp/manifests/v1.0.0

Voici les erreurs les plus fréquentes lors du déploiement d’une registry Distribution et leurs solutions.

SymptômeCause probableSolution
http: server gave HTTP response to HTTPS clientRegistry non TLSConfigurer TLS ou ajouter à insecure-registries
x509: certificate signed by unknown authorityCertificat auto-signé non reconnuCopier le CA dans /etc/docker/certs.d/
unauthorized: authentication requiredCredentials incorrectsVérifier htpasswd et docker login
manifest unknownTag inexistantVérifier le nom exact avec l’API /tags/list
denied: requested access is deniedPermissions insuffisantesVérifier la config auth dans config.yml
Espace disque pleinBlobs orphelinsExécuter le garbage collection
  1. Distribution est la registry officielle CNCF, le même code qui propulse Docker Hub. Simple, léger (~50 MB RAM), idéal pour le dev et les petits clusters.

  2. TLS est obligatoire pour un accès réseau. Utilisez Let’s Encrypt avec Traefik ou des certificats auto-signés avec configuration des clients.

  3. L’authentification htpasswd suffit pour la plupart des cas. Pour du RBAC avancé, passez à Harbor ou utilisez un serveur de tokens externe.

  4. Le garbage collection libère l’espace des blobs orphelins. Planifiez-le régulièrement, registry arrêtée ou en read-only.

  5. Pour la production enterprise, préférez Harbor : interface web, scan de sécurité, réplication, RBAC. Distribution reste excellent comme registry cache ou pour le dev.