Aller au contenu

Container Registry

200 Pratiquer ⏱ 1 h cloudscalewaydockeroci

À la fin, vous saurez

  • Organiser les espaces de noms du registre en fonction des projets et des règles d'accès de Scaleway
  • Authentifier chaque consommateur du registre avec une identité et des droits adaptés
  • Promouvoir une image d'un environnement à l'autre en conservant son empreinte
  • Mettre en place un nettoyage des étiquettes, faute de politique de rétention intégrée
  • Estimer le coût du registre et situer ce qu'il ne fait pas encore (analyse, signatures)

Prérequis

Testé avec docker 29 scaleway-cli 2.62.0 skopeo 1.13 , vérifié le 5 octobre 2026

Pourquoi

Les images de Signalements sont publiées sur GitHub Container Registry depuis le cours GitHub Actions. Chaque démarrage d'instance, chaque montée en charge de Serverless Containers, chaque nœud Kapsule qui démarre va donc chercher des couches chez un tiers, aux États-Unis, sur Internet, sous ses limites de débit. La documentation de Scaleway le déconseille explicitement pour Serverless Containers et Jobs : les limites de débit d'un registre externe peuvent faire échouer un démarrage. Le cours Cloud souverain : choisir et justifier a montré l'autre versant : la forge et le registre font partie de la chaîne de dépendances qu'un client public examine.

Le cours précédent a créé un espace de noms dans le registre de Scaleway et une identité pour y publier, dans Identité et accès. Cette leçon en fait un service exploité : qui a le droit de pousser et de tirer, comment une image passe de la préproduction à la production, combien coûte le stockage, et comment l'empêcher de grossir sans fin.

Les concepts

Espaces de noms, images, étiquettes

Le registre de Scaleway est un registre mutualisé et géré, régional, compatible avec les clients Docker et OCI. Trois niveaux :

  • l'espace de noms (namespace) appartient à un projet et à une région ; son nom doit être unique chez Scaleway (la documentation dit « globalement unique »), d'où un suffixe. Il donne la première partie de l'adresse : rg.fr-par.scw.cloud/<espace de noms> ;
  • l'image, ou dépôt, regroupe les versions d'une même application : rg.fr-par.scw.cloud/signalements-<suffixe>/signalements ;
  • l'étiquette (tag) nomme une version : :1.2.0, :sha-3f9c2e1. Chaque étiquette pointe vers une empreinte (digest), l'identifiant immuable du manifeste, sha256:....

Une étiquette peut être déplacée : :1.2.0 peut désigner demain une autre image si quelqu'un la repousse. Une empreinte, jamais. Le cours Docker : les fondamentaux en a tiré la règle : on déploie par empreinte, ou au moins par étiquette qu'on ne repousse jamais.

Le registre est disponible dans les régions fr-par, nl-ams, pl-waw et, depuis le 31 août 2026, it-mil. Les quotas d'une organisation (99 999 espaces de noms et autant d'images) ne sont pas une contrainte en pratique. Le stockage repose, depuis 2022, sur l'Object Storage de Scaleway en multi-zones à Paris, selon le journal des changements.

Visibilité

Un espace de noms est public ou privé : dans un espace public, n'importe qui peut tirer les images sans s'authentifier. Chaque image peut aussi avoir sa propre visibilité, public, private, ou inherit (celle de l'espace de noms, par défaut). Pousser exige toujours une authentification.

Une image publique par erreur, c'est le code de l'application, ses dépendances et parfois ses secrets oubliés dans une couche, offerts à tous. Pour Signalements, tout est privé.

Qui tire, qui pousse, avec quelle identité

L'authentification au registre utilise une clé d'API Scaleway : le nom d'utilisateur est un mot fixe (nologin dans la documentation de Docker ; l'exemple Kubernetes de la documentation met le nom de l'espace de noms), le mot de passe est la clé secrète. Les droits viennent de la politique IAM de l'identité qui porte la clé, et le registre n'a que deux jeux de permissions :

Jeu de permissionsPermet
ContainerRegistryReadOnlyLister et tirer
ContainerRegistryFullAccessTout : créer des espaces de noms, pousser, supprimer

Les deux s'appliquent à un projet entier : on ne peut pas donner à une identité le droit de pousser dans un seul espace de noms du projet. La granularité, c'est le projet. D'où la question d'organisation de la leçon.

Un espace de noms par projet, et la promotion

Deux contraintes documentées se combinent :

  1. les droits IAM portent sur le projet ;
  2. Serverless Containers ne peut utiliser que des images d'espaces de noms du même projet que le conteneur.

La conséquence : la production (projet signalements-prod) ne peut pas tirer, en serverless, une image rangée dans le projet de préproduction. Et si l'on rangeait tout dans un projet partagé, la CI qui pousse en préproduction aurait le droit d'écraser les images de production. La bonne organisation est donc un espace de noms par projet, et le passage d'un environnement à l'autre est une promotion : une copie de l'image, à l'identique, d'un espace de noms à l'autre.

« À l'identique » a un sens précis : la même empreinte. L'image testée en préproduction est celle qui part en production, octet pour octet. Reconstruire l'image pour la production, même à partir du même commit, n'offre pas cette garantie (cours Construire des images de conteneurs).

Ce que le registre ne fait pas, au 5 octobre 2026

  • Pas de politique de rétention. On peut stocker autant de versions que l'on veut, et rien ne les supprime. Chaque construction de CI ajoute une étiquette ; le stockage, et la facture, croissent indéfiniment. Le nettoyage est à écrire.
  • Pas d'analyse de vulnérabilités en disponibilité générale : la FAQ de Serverless Containers annonce qu'elle arrivera avec un Artifact Registry, en bêta privée. L'analyse se fait donc en CI, avec Trivy par exemple.
  • Pas de garantie documentée sur les artefacts OCI 1.1 : la documentation ne dit pas si le registre prend en charge l'API referrers, qui sert à rattacher signatures et SBOM à une image. Cosign 3 bascule sur un schéma d'étiquettes de repli quand cette API manque (cours Signer et vérifier ses images). C'est à tester sur votre espace de noms, pas à supposer.
  • Pas de journal d'audit : le registre ne fait pas partie des produits intégrés à Audit Trail (cours précédent, leçon 7). Qui a poussé quelle image ne se lit que dans les journaux de la CI.

Le coût

Selon la FAQ, au 5 octobre 2026 : les images privées coûtent 0,027 € par Go et par mois ; le trafic sortant est gratuit à l'intérieur d'une région et coûte 0,033 € par Go entre régions (Paris vers Amsterdam, par exemple) ; le trafic entrant est gratuit ; les images publiques sont gratuites jusqu'à 75 Go. Le registre a donc deux règles d'économie : supprimer ce qui ne sert plus, et tirer depuis la même région.

En pratique

Les commandes scw ont été vérifiées avec l'aide de la CLI 2.62 ; les champs JSON sont ceux des structures Namespace, Image et Tag du SDK Go. La leçon ne reproduit pas de sortie.

Les espaces de noms

Un espace de noms dans chaque projet, privé :

$ for env in preprod prod; do
    PROJET=$(scw account project list -o json \
      | jq -r --arg n "signalements-$env" '.[] | select(.name == $n) | .id')
    scw registry namespace create name="signalements-$env-<suffixe>" \
      project-id="$PROJET" is-public=false region=fr-par
  done

Vérifiez la visibilité réelle, espace de noms et images :

$ scw registry namespace list region=fr-par -o json \
    | jq -r '.[] | [.name, .is_public, .image_count, .size] | @tsv'
$ scw registry image list region=all -o json \
    | jq -r '.[] | select(.visibility == "public") | .name'

La seconde commande liste les images publiques de toutes les régions : en dehors d'un choix explicite, elle doit être vide.

S'authentifier, selon qui l'on est

Une personne, sur son poste. scw registry login configure Docker (ou Podman) avec les identifiants du profil scw courant ; scw registry install-docker-helper installe un credential helper qui fournit les identifiants à la demande, sans les écrire dans ~/.docker/config.json :

$ scw registry install-docker-helper path="$HOME/.local/bin"

La CI. L'application sig-deploiement du cours précédent a ContainerRegistryFullAccess sur le projet de préproduction. Dans GitHub Actions, la connexion suit la documentation :

      - name: Connexion au registre Scaleway
        uses: docker/login-action@v3
        with:
          registry: rg.fr-par.scw.cloud/signalements-preprod-<suffixe>
          username: nologin
          password: ${{ secrets.SCW_SECRET_KEY }}

Épinglez l'action par l'empreinte de son commit en production, comme l'explique la leçon Sécuriser ses workflows.

Une instance. L'application sig-instances, en lecture seule, du cours précédent : sa clé est installée une fois sur l'instance, dans un fichier lisible par root seul, et Docker s'authentifie avec. La leçon 8 discute de la manière de livrer cette clé.

Kapsule. Un secret de type docker-registry dans le namespace Kubernetes de l'application, référencé par imagePullSecrets dans la spécification des pods :

$ kubectl create secret docker-registry registre-scaleway \
    --namespace signalements \
    --docker-server=rg.fr-par.scw.cloud \
    --docker-username=nologin \
    --docker-password="$CLE_SECRETE_LECTURE"

La clé est celle d'une application en lecture seule. Une clé avec FullAccess dans un cluster, c'est donner à quiconque lit les secrets du namespace le droit d'écraser les images de production.

Serverless Containers et Jobs. Rien à faire : la plateforme tire avec l'application qu'elle a créée pour l'espace de noms serverless, à condition que l'image soit dans le même projet (leçon 5).

Étiquettes et empreintes

Retrouvez l'empreinte d'une étiquette :

$ IMG_ID=$(scw registry image list namespace-id="$NS_PREPROD" name=signalements \
    region=fr-par -o json | jq -r '.[0].id')
$ scw registry tag list image-id="$IMG_ID" order-by=created_at_desc region=fr-par -o json \
    | jq -r '.[] | [.name, .digest, .created_at] | @tsv'

Le filtre name= de image list et tag list est, selon l'aide de la CLI, une correspondance exacte. Le résultat montre aussi les étiquettes qui partagent une empreinte : 1.3.0 et sha-3f9c2e1 désignent souvent la même image, publiée deux fois par la CI.

Promouvoir sans reconstruire

skopeo copie une image d'un registre à l'autre sans démon Docker, avec toutes ses plateformes (--all) et en échouant si l'empreinte ne peut pas être conservée (--preserve-digests) :

$ EMPREINTE=$(scw registry tag list image-id="$IMG_ID" name=1.3.0 region=fr-par -o json \
    | jq -r '.[0].digest')
$ skopeo copy --all --preserve-digests \
    --src-creds "nologin:$CLE_LECTURE_PREPROD" \
    --dest-creds "nologin:$CLE_ECRITURE_PROD" \
    "docker://rg.fr-par.scw.cloud/signalements-preprod-<suffixe>/signalements@$EMPREINTE" \
    "docker://rg.fr-par.scw.cloud/signalements-prod-<suffixe>/signalements:1.3.0"
  • La source est désignée par empreinte : même si quelqu'un repousse :1.3.0 en préproduction entre-temps, c'est l'image testée qui part.
  • La destination reçoit l'étiquette 1.3.0, qu'on ne repoussera jamais.
  • Deux identités : une clé de lecture sur la préproduction, une clé d'écriture sur la production. Dans la CI, cette étape vit dans un job distinct, rattaché à l'environnement GitHub production avec relecture obligatoire (cours GitHub Actions, leçon 9).

Vérifiez que l'empreinte est identique des deux côtés avec la commande tag list de la section précédente, sur chaque espace de noms. C'est cette empreinte que le déploiement de production référence.

Nettoyer

Faute de politique de rétention, un script supprime les étiquettes de CI anciennes. Celui-ci garde, pour chaque image, les dix étiquettes sha-* les plus récentes, et ne touche jamais aux étiquettes de version :

#!/usr/bin/env bash
# Supprime les étiquettes de CI (sha-*) anciennes d'un espace de noms du registre,
# en gardant les GARDER plus récentes de chaque image. Les étiquettes de version
# (1.2.0, 1.3.0...) ne sont jamais touchées.
# Usage : ./nettoyer-registre.sh <id-espace-de-noms> [--supprimer]
set -euo pipefail
NS_ID=${1:?usage : $0 <id-espace-de-noms> [--supprimer]}
MODE=${2:-}
GARDER=${GARDER:-10}

scw registry image list namespace-id="$NS_ID" region=fr-par -o json \
  | jq -r '.[] | "\(.id) \(.name)"' \
  | while read -r image_id image_nom; do
      scw registry tag list image-id="$image_id" order-by=created_at_desc region=fr-par -o json \
        | jq -r --argjson garder "$GARDER" \
            '[.[] | select(.name | startswith("sha-"))] | .[$garder:] | .[] | "\(.id) \(.name) \(.digest)"' \
        | while read -r tag_id tag_nom empreinte; do
            if [ "$MODE" = "--supprimer" ]; then
              echo "suppression de $image_nom:$tag_nom ($empreinte)"
              scw registry tag delete "$tag_id" region=fr-par > /dev/null
            else
              echo "serait supprimée : $image_nom:$tag_nom ($empreinte)"
            fi
          done
    done

Sans --supprimer, le script n'affiche que ce qu'il ferait : lancez-le d'abord ainsi. La politique (« dix dernières étiquettes de CI, toutes les versions ») est un exemple ; écrivez la vôtre, et notez-la dans le dépôt. Planifiez le script en job (leçon 6) avec une application qui a ContainerRegistryFullAccess sur le seul projet concerné.

Warning

Deux étiquettes peuvent partager une empreinte (sha-3f9c2e1 et 1.3.0). L'aide de la CLI 2.62 indique que la suppression d'une étiquette dont l'empreinte est partagée échouait sans l'option force, aujourd'hui marquée comme dépréciée ; la documentation ne décrit pas le comportement actuel. Avant de planifier le script en production, vérifiez sur un espace de noms d'essai que supprimer sha-3f9c2e1 laisse 1.3.0 intacte et tirable.

Signatures et artefacts : tester avant de croire

Signez une image de préproduction avec cosign, comme dans le cours Signer et vérifier ses images, puis regardez ce qui lui est rattaché :

$ cosign tree rg.fr-par.scw.cloud/signalements-preprod-<suffixe>/signalements@"$EMPREINTE"

La commande liste les signatures et attestations rattachées à l'image, quel que soit le mécanisme utilisé (API referrers ou étiquettes de repli). Si cosign verify réussit, la chaîne fonctionne pour votre usage. Notez aussi que la promotion par skopeo copy --all ne copie pas les artefacts rattachés : il faut les copier ou signer de nouveau en production, ce que fait la politique de signature décrite dans le cours cité.

Sous le capot

Le protocole. Un docker pull est une suite de requêtes HTTP définies par la spécification OCI Distribution : demander le manifeste par étiquette ou par empreinte, puis télécharger chaque couche (blob) par son empreinte. Un docker push envoie d'abord les couches absentes, puis le manifeste, puis l'étiquette. Les couches sont adressées par leur contenu : un registre n'a pas besoin de stocker deux fois une couche identique, et un client n'envoie pas une couche que le registre possède déjà. La documentation de Scaleway ne précise pas comment le stockage facturé compte les couches partagées ; mesurez avec le champ size de l'espace de noms plutôt que d'additionner les tailles des images.

L'authentification. Le client Docker obtient du registre un jeton à partir de l'utilisateur et du mot de passe fournis, puis présente ce jeton à chaque requête. Pour Scaleway, ce mot de passe est la clé secrète d'une clé d'API, et les droits sont ceux de la politique IAM de son porteur : le registre ne connaît pas d'utilisateurs propres. Un credential helper est un petit programme que Docker appelle pour obtenir ces identifiants au moment voulu, au lieu de les lire dans son fichier de configuration.

Une image multi-architecture est un index : un manifeste qui liste un manifeste par plateforme. Le client choisit celui de sa plateforme. Serverless Containers et Jobs exigent linux/amd64 : un index qui en contient une variante convient en pratique, une image construite seulement pour arm64 échoue. Kapsule, lui, peut avoir des nœuds arm64 selon les types choisis.

Pièges courants

denied: requested access to the resource is denied au push. Le plus souvent, l'identité n'a que ContainerRegistryReadOnly, ou sa politique porte sur un autre projet que celui de l'espace de noms. Vérifiez la politique, pas la clé.

Une image introuvable en production serverless. Elle est dans l'espace de noms d'un autre projet. Serverless Containers ne tire que dans son projet : promouvez-la.

Le stockage double chaque trimestre. Aucune rétention, une étiquette par commit. Le script de nettoyage, planifié, règle la question ; mesurez avant et après avec size de l'espace de noms.

:latest en production. L'étiquette la plus mobile qui soit. Une instance redémarrée tire ce qu'elle trouve, pas ce qui a été testé.

Une clé de CI dans un ~/.docker/config.json partagé. docker login écrit les identifiants, encodés en base64 mais non chiffrés, dans ce fichier. Sur un agent de CI réutilisé, ils survivent au job. Utilisez des agents éphémères, un credential helper, ou un docker logout en fin de job.

Du trafic payant inattendu. Des nœuds à Amsterdam qui tirent depuis le registre de Paris paient le trafic entre régions. Un espace de noms dans chaque région utilisée, alimenté par promotion, coûte moins cher et démarre plus vite.

Sécurité

Le registre est un point de la chaîne d'approvisionnement. Quiconque peut pousser une image peut faire exécuter son code en production à la prochaine montée en charge. Les identités qui écrivent doivent être rares (la CI, et la promotion), séparées par environnement, avec des clés qui expirent, et l'écriture en production doit passer par une étape relue.

FullAccess ne se divise pas. Le jeu d'écriture permet aussi de supprimer, d'écraser et de rendre publique une image. C'est un argument de plus pour des projets séparés : une clé de préproduction volée ne touche pas la production.

Pas d'audit côté registre. Faute d'intégration avec Audit Trail, conservez les journaux de la CI qui poussent, et déployez par empreinte : une image écrasée sous la même étiquette ne sera pas déployée par erreur.

Analyser et signer en CI. Tant que le registre n'analyse pas lui-même les images, Trivy dans le pipeline, avant la publication, est la barrière (cours Gestion des vulnérabilités). La signature avec cosign et la vérification au déploiement ferment la boucle, sous réserve d'avoir testé le comportement du registre avec les artefacts rattachés.

La visibilité se vérifie. Une commande planifiée qui liste les images publiques et alerte si la liste n'est pas vide coûte trois lignes.

En production

  • Un espace de noms par projet et par région utilisée, alimentés par promotion ; la production ne reçoit que des images déjà testées, par empreinte.
  • Une politique de rétention écrite, appliquée par un job, revue quand la cadence de livraison change. Gardez toutes les versions publiées en production pendant au moins la durée pendant laquelle vous pourriez vouloir revenir en arrière.
  • Un miroir des images de base. Les images python:3.13-slim et consorts, tirées depuis Docker Hub à chaque construction, subissent ses limites de débit ; les copier dans un espace de noms dédié, mis à jour régulièrement, fiabilise les constructions.
  • Chez Lyneko, les images des applications sont publiées dans le registre rg.fr-par.scw.cloud/lyneko-apps, à Paris, à côté du cluster Kapsule qui les exécute ; la CI y pousse, et le dépôt de déploiement référence l'étiquette publiée (cours GitOps avec Argo CD).

Exercices

1. Organisation (niveau 200). Une équipe propose un seul espace de noms signalements dans un projet outillage, partagé par la préproduction et la production, « pour ne pas dupliquer le stockage ». Donnez deux raisons, l'une d'accès, l'autre technique, pour lesquelles cette organisation ne convient pas à Signalements, et ce que coûte la vôtre.

Solution

Accès : les droits IAM du registre portent sur le projet ; la CI de préproduction, qui doit pousser, aurait le droit d'écraser ou de supprimer les images utilisées en production. Technique : Serverless Containers ne tire que des images du même projet ; la production serverless ne pourrait pas utiliser ce registre. L'organisation par projet coûte un peu de stockage en double (seules les versions promues sont copiées, 0,027 € par Go et par mois) et une étape de promotion dans la CI, qui est justement l'endroit où placer la relecture.

2. Promotion (niveau 200). Pourquoi la commande de promotion désigne-t-elle la source par empreinte plutôt que par l'étiquette 1.3.0 ? Décrivez un scénario où l'étiquette conduirait à déployer en production une image jamais testée.

Solution

L'étiquette est mobile. Scénario : la CI publie 1.3.0 en préproduction, la recette la valide le lundi ; le mardi, un correctif est poussé par erreur sous la même étiquette 1.3.0 (une construction relancée, un développeur pressé) ; la promotion du mercredi, par étiquette, copie le correctif non testé. Par empreinte, la promotion copie exactement l'image validée, et --preserve-digests garantit qu'elle arrive identique en production.

3. Coût (niveau 200). Un espace de noms contient 400 étiquettes de CI, chacune ajoutant en moyenne 60 Mo de couches propres (le reste est partagé), et 30 versions publiées. Estimez le stockage dû aux étiquettes de CI et son coût mensuel aux tarifs du 5 octobre 2026, puis ce qu'économise le script avec GARDER=10.

Solution

400 × 60 Mo = 24 Go environ, soit 24 × 0,027 ≈ 0,65 € par mois. Avec dix étiquettes gardées, 10 × 60 Mo = 0,6 Go : l'économie est d'environ 0,63 € par mois. La somme est faible ; ce qui compte est la tendance (le stockage croît à chaque commit, indéfiniment) et la lisibilité du registre. Attention : une couche n'est libérée que si plus aucune étiquette conservée ne la référence, et les versions publiées qui partagent l'empreinte d'une étiquette sha-* supprimée doivent rester tirables, ce qu'il faut vérifier.

Récapitulatif

  • Le registre est régional, organisé en espaces de noms (au nom unique), images et étiquettes qui pointent vers des empreintes.
  • Visibilité par espace de noms et par image (inherit, public, private) ; tout privé, et une vérification régulière.
  • Authentification par clé d'API (nologin et la clé secrète) ; deux jeux de permissions, par projet : lecture ou tout.
  • Un espace de noms par projet, et une promotion par copie à empreinte constante (skopeo copy --all --preserve-digests, source par empreinte).
  • Pas de rétention, pas d'analyse de vulnérabilités en disponibilité générale, pas d'audit, prise en charge des artefacts OCI 1.1 non documentée au 5 octobre 2026 : nettoyage scripté, Trivy en CI, signatures testées.
  • Coût : stockage privé au Go et au mois, trafic gratuit dans la région.

Pour aller plus loin

  • La spécification OCI Distribution, pour comprendre ce que font réellement pull et push.
  • Le cours Construire des images de conteneurs, pour des images petites (moins de stockage, démarrages plus rapides) et signées.
  • La leçon suivante, qui range les clés de lecture et d'écriture du registre dans Secret Manager.
Voir ma constellation →

Sources