CleanStart container images are distributed via registry.cleanstart.com, but production deployments should never pull directly from the public registry on every deployment. Instead, mirror CleanStart images to your own enterprise registry infrastructure—AWS ECR, Azure ACR, Google Cloud Artifact Registry, on-premises Harbor, JFrog Artifactory, or Nexus. This approach provides resilience, performance, compliance, cost control, security, audit trails, and supply chain control. This guide covers registry strategies, automation, and integration patterns for every major platform.
Mirror and Proxy Strategies
Pull-Through Proxy (Easiest)
Some registries support pull-through proxies, which automatically pull images from remote sources when they don't exist locally. When you request an image that doesn't exist in your registry, the system automatically fetches it from the remote source, caches it, and serves it to you. Subsequent requests hit the cache instead of going back to the remote source.
The pull-through proxy approach has significant advantages. You don't need to manually mirror images since the process is transparent and automatic. Your deployment automation requires no changes to work with the cached images. However, this approach also has drawbacks. The first pull from registry.cleanstart.com introduces latency as the image is fetched from the remote. Network failures can block pulls if the remote registry becomes unavailable.
Pull-through proxy support varies by registry platform. Harbor supports it, as does Artifactory and Nexus. However, ECR, ACR, and GCR do not support pull-through proxies natively.
Full Mirror (Recommended for Production)
A full mirror approach periodically syncs CleanStart images to your registry using mirroring tools like crane, skopeo, or registry-native APIs. New releases are detected automatically through cron jobs or CI/CD webhooks that trigger periodic syncs.
This approach guarantees that images are always available locally, providing predictable performance and availability. The images integrate seamlessly with your existing scanning and signing pipelines. However, full mirroring requires maintaining mirroring automation infrastructure and storing multiple copies of images in your registry, which increases storage costs.
AWS Elastic Container Registry (ECR)
Step 1: Create ECR Repository
# Create a repository for CleanStart base imagesaws ecr create-repository \ --repository-name cleanstart/base \ --region us-east-1 \ --image-scanning-configuration scanOnPush=true # Create additional repos for application layersaws ecr create-repository \ --repository-name myapp/runtime \ --region us-east-1 \ --image-scanning-configuration scanOnPush=trueStep 2: Authenticate and Push
# Authenticate Docker with ECRaws ecr get-login-password --region us-east-1 | \ docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com # Pull from registry.cleanstart.com, tag for ECR, and pushECR_REPO="123456789012.dkr.ecr.us-east-1.amazonaws.com"docker pull registry.cleanstart.com/cleanstart/base:latestdocker tag registry.cleanstart.com/cleanstart/base:latest \ $ECR_REPO/cleanstart/base:latestdocker push $ECR_REPO/cleanstart/base:latest # Verify image in ECRaws ecr describe-images \ --repository-name cleanstart/base \ --region us-east-1Step 3: ECR Lifecycle Policies
Automatically delete old image tags to control storage costs:
{ "rules": [ { "rulePriority": 1, "description": "Keep latest 5 images, remove older", "selection": { "tagStatus": "tagged", "tagPrefixList": ["v"], "countType": "imageCountMoreThan", "countNumber": 5 }, "action": { "type": "expire" } }, { "rulePriority": 2, "description": "Remove untagged images after 7 days", "selection": { "tagStatus": "untagged", "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 7 }, "action": { "type": "expire" } } ]}Apply the policy:
aws ecr put-lifecycle-policy \ --repository-name cleanstart/base \ --lifecycle-policy-text file://lifecycle-policy.json \ --region us-east-1Step 4: EKS Image Pull Configuration
In your Kubernetes manifests, reference the ECR image and configure pull secrets:
apiVersion: v1kind: Namespacemetadata: name: production---apiVersion: v1kind: Secretmetadata: name: ecr-pull-secret namespace: productiontype: kubernetes.io/dockercfgdata: .dockercfg: <base64-encoded-docker-config>---apiVersion: apps/v1kind: Deploymentmetadata: name: myapp namespace: productionspec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: imagePullSecrets: - name: ecr-pull-secret containers: - name: app image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp/runtime:v1.2.0 ports: - containerPort: 8080Alternative: Use IAM roles for service accounts (IRSA) to avoid secrets:
# Create IAM role for EKS service accountaws iam create-role \ --role-name EKSECRPullRole \ --assume-role-policy-document file://trust-policy.json # Attach ECR read policyaws iam attach-role-policy \ --role-name EKSECRPullRole \ --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly # Annotate Kubernetes service accountkubectl annotate serviceaccount default \ -n production \ eks.amazonaws.com/role-arn=arn:aws:iam::123456789012:role/EKSECRPullRoleStep 5: Automation Script for Syncing CleanStart Images
Create a Lambda function or EC2 cron job to periodically mirror CleanStart images:
#!/usr/bin/env python3"""Sync CleanStart images from registry.cleanstart.com to AWS ECR.Run this daily or weekly to stay up-to-date.""" import boto3import subprocessimport jsonfrom datetime import datetime ECR_REGISTRY = "123456789012.dkr.ecr.us-east-1.amazonaws.com"CLEANSTART_REGISTRY = "registry.cleanstart.com"IMAGES_TO_SYNC = [ "cleanstart/base:latest", "cleanstart/runtime-python:3.12", "cleanstart/runtime-nodejs:20", "cleanstart/runtime-go:1.21",] def get_ecr_login_token(): """Authenticate with ECR.""" client = boto3.client("ecr", region_name="us-east-1") response = client.get_authorization_token() auth_data = response["authorizationData"][0] return auth_data["authorizationToken"], auth_data["proxyEndpoint"] def sync_image(source_image: str, dest_repo: str): """Pull from registry.cleanstart.com, tag for ECR, and push.""" source_uri = f"{CLEANSTART_REGISTRY}/{source_image}" dest_uri = f"{ECR_REGISTRY}/{dest_repo}" print(f"Syncing {source_uri} → {dest_uri}") try: # Pull from public registry subprocess.run(["docker", "pull", source_uri], check=True) # Tag for ECR subprocess.run(["docker", "tag", source_uri, dest_uri], check=True) # Push to ECR subprocess.run(["docker", "push", dest_uri], check=True) # Record in DynamoDB for audit dynamodb = boto3.resource("dynamodb") table = dynamodb.Table("image-sync-log") table.put_item( Item={ "sync_id": f"{source_image}#{datetime.utcnow().isoformat()}", "source": source_uri, "destination": dest_uri, "timestamp": datetime.utcnow().isoformat(), "status": "success", } ) print(f"✅ Successfully synced {source_image}") except subprocess.CalledProcessError as e: print(f"❌ Failed to sync {source_image}: {e}") # Optionally send alert to SNS/CloudWatch raise def main(): """Sync all configured CleanStart images to ECR.""" print(f"Starting CleanStart image sync at {datetime.utcnow().isoformat()}") # Authenticate token, endpoint = get_ecr_login_token() subprocess.run( ["docker", "login", "-u", "AWS", "-p", token, endpoint], check=True, capture_output=True ) # Sync each image for image in IMAGES_TO_SYNC: parts = image.split(":") repo = f"cleanstart/{parts[0].split('/')[-1]}" # Extract repo name sync_image(image, f"{repo}:{parts[1]}") print(f"Sync complete at {datetime.utcnow().isoformat()}") if __name__ == "__main__": main()Deploy this as a scheduled Lambda or cron job:
# CloudWatch Events rule (EventBridge)aws events put-rule \ --name cleanstart-image-sync \ --schedule-expression "cron(0 2 * * ? *)" # Daily at 2 AM UTCAzure Container Registry (ACR)
Step 1: Create ACR
# Create resource groupaz group create \ --name rg-containers \ --location eastus # Create ACRaz acr create \ --resource-group rg-containers \ --name myorgacr \ --sku Standard \ --admin-enabled trueStep 2: Import Images from registry.cleanstart.com
# Login to ACRaz acr login --name myorgacr # Import image directly (no local pull needed)az acr import \ --name myorgacr \ --source registry.cleanstart.com/cleanstart/base:latest \ --image cleanstart/base:latest # Import with tagaz acr import \ --name myorgacr \ --source registry.cleanstart.com/cleanstart/runtime-python:3.12 \ --image cleanstart/runtime-python:3.12Step 3: ACR Tasks for Automated Imports
Create a scheduled task to keep images up-to-date:
# acr-import-task.yamlversion: v1.1.0steps: - cmd: acr import --name myorgacr --source registry.cleanstart.com/cleanstart/base:latest --image cleanstart/base:latest - cmd: acr import --name myorgacr --source registry.cleanstart.com/cleanstart/runtime-python:3.12 --image cleanstart/runtime-python:3.12 - cmd: acr import --name myorgacr --source registry.cleanstart.com/cleanstart/runtime-nodejs:20 --image cleanstart/runtime-nodejs:20Register the task:
az acr task create \ --registry myorgacr \ --name sync-cleanstart-images \ --file acr-import-task.yaml \ --schedule "0 2 * * *" # Daily at 2 AMStep 4: AKS Integration
Attach ACR to your AKS cluster:
# Get AKS cluster name and resource groupAKS_CLUSTER="myaks"AKS_RG="rg-aks"ACR_NAME="myorgacr"ACR_RG="rg-containers" # Get ACR resource IDACR_ID=$(az acr show --resource-group $ACR_RG --name $ACR_NAME --query id --output tsv) # Attach ACR to AKSaz aks update \ --resource-group $AKS_RG \ --name $AKS_CLUSTER \ --attach-acr $ACR_IDIn your Kubernetes deployment:
apiVersion: apps/v1kind: Deploymentmetadata: name: myapp namespace: defaultspec: replicas: 2 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: app image: myorgacr.azurecr.io/cleanstart/base:latest ports: - containerPort: 8080Step 5: Geo-Replication for Multi-Region
Replicate images to multiple Azure regions for lower latency:
# Enable geo-replicationaz acr replication create \ --registry myorgacr \ --location westus az acr replication create \ --registry myorgacr \ --location northeurope # List replicationsaz acr replication list --registry myorgacrOnce configured, images pushed to the primary ACR automatically replicate to all configured regions.
Google Cloud Artifact Registry / GCR
Step 1: Create Artifact Registry Repository
# Set projectexport PROJECT_ID="my-gcp-project"gcloud config set project $PROJECT_ID # Create repositorygcloud artifacts repositories create cleanstart \ --repository-format=docker \ --location=us-central1 \ --description="CleanStart images"Step 2: Push Images
# Configure Docker for Artifact Registrygcloud auth configure-docker us-central1-docker.pkg.dev # Pull from public registrydocker pull registry.cleanstart.com/cleanstart/base:latest # Tag for Artifact Registrydocker tag registry.cleanstart.com/cleanstart/base:latest \ us-central1-docker.pkg.dev/$PROJECT_ID/cleanstart/base:latest # Push to Artifact Registrydocker push us-central1-docker.pkg.dev/$PROJECT_ID/cleanstart/base:latest # Verifygcloud artifacts docker images list us-central1-docker.pkg.dev/$PROJECT_ID/cleanstartStep 3: GKE Workload Identity Integration
Use Workload Identity for secure, keyless image pulls:
# Create Google Service Accountgcloud iam service-accounts create gke-image-puller \ --display-name="GKE Image Puller" # Grant Artifact Registry read permissionsgcloud projects add-iam-policy-binding $PROJECT_ID \ --member="serviceAccount:gke-image-puller@$PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/artifactregistry.reader" # Create Kubernetes namespace and service accountkubectl create namespace productionkubectl create serviceaccount gke-workload -n production # Bind Kubernetes SA to Google SAgcloud iam service-accounts add-iam-policy-binding \ gke-image-puller@$PROJECT_ID.iam.gserviceaccount.com \ --role roles/iam.workloadIdentityUser \ --member "serviceAccount:$PROJECT_ID.svc.id.goog[production/gke-workload]" # Annotate Kubernetes service accountkubectl annotate serviceaccount gke-workload \ -n production \ iam.gke.io/gcp-service-account=gke-image-puller@$PROJECT_ID.iam.gserviceaccount.comIn your Kubernetes manifest:
apiVersion: apps/v1kind: Deploymentmetadata: name: myapp namespace: productionspec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: serviceAccountName: gke-workload containers: - name: app image: us-central1-docker.pkg.dev/my-gcp-project/cleanstart/base:latest ports: - containerPort: 8080Harbor (On-Premises)
Step 1: Project Setup
# Install Harbor (or use existing instance)# https://goharbor.io/docs/latest/install-config/ # Access Harbor web UI (typically https://harbor.example.com)# Login with admin credentialsIn the Harbor UI:
- Create a new project:
cleanstart - Set access level: "Public" (so Kubernetes nodes can pull without secrets)
- Enable "Scan on push" under the Configuration menu
Step 2: Replication Rules
Configure Harbor to automatically replicate images from registry.cleanstart.com:
# Harbor Replication Rule (via REST API or UI){ "name": "sync-cleanstart-base", "description": "Replicate CleanStart base images from registry.cleanstart.com", "src_registry_id": -1, # -1 means remote (registry.cleanstart.com) "src_namespaces": ["cleanstart"], "dest_registry_id": 0, # 0 means local Harbor "dest_namespace": "cleanstart", "dest_repo_name_template": "{repo}", "trigger": { "type": "scheduled", "trigger_settings": { "cron": "0 2 * * *" # Daily at 2 AM } }, "override": true, "enabled": true}Apply via Harbor API:
curl -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Basic <base64-admin-credentials>" \ -d @replication-rule.json \ https://harbor.example.com/api/v2.0/replication/policiesOr use Harbor UI: Administration → Replication Management → New Rule
Step 3: Vulnerability Scanning Integration
Harbor includes built-in vulnerability scanning (Trivy). After images are replicated, they're automatically scanned.
View results in Harbor UI or via API:
curl -H "Authorization: Basic <credentials>" \ https://harbor.example.com/api/v2.0/projects/cleanstart/repositories/base/artifacts/latest/scan/reportStep 4: Robot Accounts for CI/CD
Create a robot account for your CI/CD system to pull/push images:
# In Harbor UI: Administration → Robot Accounts → Create Robot Account# Or via API: curl -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Basic <base64-admin-credentials>" \ -d '{ "name": "cicd-puller", "description": "CI/CD system image pulls", "access": [ { "resource": "/project/cleanstart/repository", "action": ["pull"] } ] }' \ https://harbor.example.com/api/v2.0/robotsUse in CI/CD (e.g., GitHub Actions):
- name: Login to Harbor run: | docker login -u robot$cicd-puller -p "$HARBOR_ROBOT_TOKEN" \ harbor.example.com - name: Pull CleanStart image run: | docker pull harbor.example.com/cleanstart/base:latestJFrog Artifactory
Step 1: Create Remote Repository
Configure a remote repository pointing to registry.cleanstart.com:
curl -X PUT \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ARTIFACTORY_TOKEN" \ -d '{ "key": "cleanstart-remote", "packageType": "Docker", "repoLayoutRef": "simple-default", "remoteRepoLayoutRef": "simple-default", "url": "https://registry.cleanstart.com", "xrayIndex": true, "blackedOut": false }' \ https://artifactory.example.com/artifactory/api/repositories/cleanstart-remoteStep 2: Create Virtual Repository
Combine remote and local repositories in a virtual repository for transparent proxying:
curl -X PUT \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ARTIFACTORY_TOKEN" \ -d '{ "key": "cleanstart", "packageType": "Docker", "repoLayoutRef": "simple-default", "repositories": [ "cleanstart-local", "cleanstart-remote" ], "defaultDeploymentRepo": "cleanstart-local", "description": "Virtual Docker repository for CleanStart" }' \ https://artifactory.example.com/artifactory/api/repositories/cleanstartNow, pulling from artifactory.example.com/cleanstart/base:latest will:
- Check the local repository
- If not found, pull from
cleanstart-remote(registry.cleanstart.com) - Cache locally for future pulls
Step 3: Xray Integration
Enable Xray scanning for vulnerability detection:
curl -X POST \ -H "Authorization: Bearer $ARTIFACTORY_TOKEN" \ -d '{ "name": "cleanstart-scan-policy", "type": "security_vulnerability", "rules": [ { "criteria": { "any_package": true }, "actions": { "webhooks": ["send-alert"], "block_download": true, "block_release_bundle_distribution": true } } ], "enabled": true }' \ https://artifactory.example.com/xray/api/v2/policiesNexus Repository
Step 1: Create Docker Proxy Repository
curl -X POST \ -H "Content-Type: application/json" \ -u admin:$NEXUS_PASSWORD \ -d '{ "name": "cleanstart-proxy", "format": "docker", "type": "proxy", "proxy": { "remoteUrl": "https://registry.cleanstart.com", "contentMaxAge": 1440, "metadataMaxAge": 1440 }, "httpClient": { "blocked": false, "autoBlock": false, "connection": { "retries": 0, "userAgentSuffix": "nexus-3" } }, "storage": { "blobStoreName": "default", "strictContentTypeValidation": true }, "routingRules": [] }' \ https://nexus.example.com/service/rest/v1/repositories/docker/proxyStep 2: Create Hosted Repository
For your own custom builds:
curl -X POST \ -H "Content-Type: application/json" \ -u admin:$NEXUS_PASSWORD \ -d '{ "name": "myapp-hosted", "format": "docker", "type": "hosted", "storage": { "blobStoreName": "default", "strictContentTypeValidation": true, "writePolicy": "allow_once" } }' \ https://nexus.example.com/service/rest/v1/repositories/docker/hostedStep 3: Create Group Repository
Combine proxy and hosted repositories:
curl -X POST \ -H "Content-Type: application/json" \ -u admin:$NEXUS_PASSWORD \ -d '{ "name": "cleanstart-group", "format": "docker", "type": "group", "group": { "memberNames": ["cleanstart-proxy", "myapp-hosted"] }, "storage": { "blobStoreName": "default", "strictContentTypeValidation": true } }' \ https://nexus.example.com/service/rest/v1/repositories/docker/groupStep 4: Cleanup Policies
Prevent unbounded storage growth:
curl -X POST \ -H "Content-Type: application/json" \ -u admin:$NEXUS_PASSWORD \ -d '{ "name": "cleanup-old-images", "format": "docker", "type": "hosted", "notes": "Remove untagged images after 30 days", "criteria": { "isPrerelease": null, "lastBlobUpdatedBefore": 30, "lastDownloadedBefore": null, "releaseType": null, "regex": null, "searchUpstreamUsingRegex": false } }' \ https://nexus.example.com/service/rest/v1/cleanup-policiesAssign to repositories via the web UI.
Air-Gapped Registries
For environments without internet access, pre-load CleanStart images into an isolated registry.
See: ../../04-deploy/air-gapped/air-gapped-deployment.md for complete air-gapped setup instructions, including: Building a registry appliance, Using skopeo and crane for offline image transfer, Configuring Kubernetes to pull from air-gapped registry, and Managing image updates in isolated networks.
Image Signing and Verification
After mirroring images to your registry, verify their authenticity using Cosign and CleanStart's public signing key.
Verify Signature on Mirrored Image
# Install cosignbrew install sigstore/tap/cosign # Verify an image with CleanStart's public keycosign verify \ --key https://public-keys.clnstrt.dev/cleanstart.pub \ registry.cleanstart.com/cleanstart/base:latest # Output example:# Verification successful!# Verified image at: registry.cleanstart.com/cleanstart/base:latest# Certificate subject: CN=build@cleanstart.devAfter pushing to your registry, the signature travels with the image:
# Verify after pushing to ECRcosign verify \ --key https://public-keys.clnstrt.dev/cleanstart.pub \ 123456789012.dkr.ecr.us-east-1.amazonaws.com/cleanstart/base:latestEnforce Signature Verification in Kubernetes
Use admission controllers (e.g., Sigstore's policy-controller) to reject unsigned images:
apiVersion: admissionregistration.k8s.io/v1kind: ClusterPolicymetadata: name: cleanstart-signature-checkspec: # Reject any image that doesn't have a valid signature authorities: - keySecret: name: cleanstart-public-key namespace: cosign-system images: - glob: "*.dkr.ecr.us-east-1.amazonaws.com/cleanstart/*" - glob: "artifactory.example.com/cleanstart/*"Automated Sync Pipelines
Option 1: Scheduled Cron Job in Kubernetes
apiVersion: batch/v1kind: CronJobmetadata: name: sync-cleanstart-images namespace: kube-systemspec: schedule: "0 2 * * *" # Daily at 2 AM UTC jobTemplate: spec: template: spec: serviceAccountName: image-sync containers: - name: sync image: alpine:latest command: - /bin/sh - -c - | apk add curl jq # Use crane or skopeo to sync images # Example: sync to ECR IMAGES=( "registry.cleanstart.com/cleanstart/base:latest" "registry.cleanstart.com/cleanstart/runtime-python:3.12" ) for img in "${IMAGES[@]}"; do echo "Syncing $img..." # Use crane copy for efficient mirroring crane copy "$img" "$ECR_REGISTRY/$(basename $img)" done env: - name: ECR_REGISTRY value: "123456789012.dkr.ecr.us-east-1.amazonaws.com" restartPolicy: OnFailure---apiVersion: v1kind: ServiceAccountmetadata: name: image-sync namespace: kube-system---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata: name: image-syncrules: - apiGroups: [""] resources: ["secrets"] verbs: ["get"] # Access registry credentials---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: image-syncroleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: image-syncsubjects: - kind: ServiceAccount name: image-sync namespace: kube-systemOption 2: GitHub Actions Workflow
name: Sync CleanStart Images to ECR on: schedule: - cron: "0 2 * * *" # Daily at 2 AM UTC workflow_dispatch: # Allow manual trigger permissions: contents: read jobs: sync: runs-on: ubuntu-latest steps: - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - name: Login to ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v2 - name: Sync CleanStart images env: REGISTRY: ${{ steps.login-ecr.outputs.registry }} run: | IMAGES=( "cleanstart/base:latest" "cleanstart/runtime-python:3.12" "cleanstart/runtime-nodejs:20" ) for img in "${IMAGES[@]}"; do SOURCE="registry.cleanstart.com/$img" DEST="$REGISTRY/cleanstart/$(echo $img | cut -d: -f1 | cut -d/ -f2):$(echo $img | cut -d: -f2)" echo "Syncing $SOURCE → $DEST" docker pull "$SOURCE" docker tag "$SOURCE" "$DEST" docker push "$DEST" done - name: Send Slack notification if: always() uses: slackapi/slack-github-action@v1 with: webhook-url: ${{ secrets.SLACK_WEBHOOK }} payload: | { "text": "CleanStart image sync: ${{ job.status }}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "*CleanStart Image Sync*\nStatus: ${{ job.status }}" } } ] }Option 3: GitLab CI
sync-cleanstart-images: stage: deploy image: alpine:latest script: - apk add curl docker-cli - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - | IMAGES=( "registry.cleanstart.com/cleanstart/base:latest" "registry.cleanstart.com/cleanstart/runtime-python:3.12" ) for img in "${IMAGES[@]}"; do docker pull "$img" NEW_NAME="$CI_REGISTRY/myorg/$(basename $img)" docker tag "$img" "$NEW_NAME" docker push "$NEW_NAME" done only: - schedules # Trigger via scheduled pipeline retry: max: 2 when: - runner_system_failure - stuck_or_timeout_failureNamespace and Tagging Conventions
Adopt a consistent naming scheme across all registries:
Recommended Scheme
REGISTRY_HOST/NAMESPACE/REPOSITORY:TAG Examples: 123456789012.dkr.ecr.us-east-1.amazonaws.com/cleanstart/base:v1.2.0 123456789012.dkr.ecr.us-east-1.amazonaws.com/cleanstart/base:latest myorgacr.azurecr.io/cleanstart/runtime-python:3.12-slim us-central1-docker.pkg.dev/my-project/cleanstart/runtime-nodejs:20-alpine artifactory.example.com/cleanstart/base:v1.2.0-build+001 harbor.example.com/cleanstart/base:v1.2.0@sha256:abc123...Tag Strategy
Use semantic versioning and variant tags to organize your images. The latest tag should point to the most recent stable release. Semantic versions like v1.2.0 represent specific releases and are immutable. Major version tags like v1 point to the latest version in that major series. Variant tags like v1.2.0-slim or v1.2.0-fips represent build variants. Build metadata like v1.2.0-build+20240315 captures CI build numbers.
Namespace Organization
Your registry should be organized into logical namespaces for clarity and access control. The cleanstart/ namespace contains all base and runtime images maintained by the platform team. This includes the base OS image and language-specific runtimes for Python, Node.js, Go, and Java.
The myapp/ namespace (or your application name) contains application-specific services. These include the api service for main business logic, the worker service for background job processing, and the dashboard service for web frontends. This separation makes it easy to find application components and manage access permissions by team.
The infrastructure/ namespace houses shared infrastructure components that support multiple applications. This includes custom ingress controllers (like nginx-ingress), certificate management systems (cert-manager), and observability stacks for monitoring and logging. By organizing namespaces this way, you create clear ownership boundaries and simplify access control policies.
Implement in Kubernetes
Use Kyverno to enforce naming policies:
apiVersion: kyverno.io/v1kind: ClusterPolicymetadata: name: enforce-image-registryspec: validationFailureAction: audit rules: - name: require-registry-prefix match: resources: kinds: - Pod validate: message: "Image must be from approved registry" pattern: spec: containers: - image: "123456789012.dkr.ecr.us-east-1.amazonaws.com/* | artifactory.example.com/*" - name: require-image-tag match: resources: kinds: - Pod validate: message: "Image tag must not be 'latest'" pattern: spec: containers: - image: "*:*"Conclusion
Mirroring CleanStart images to your enterprise registry ensures production resilience, compliance, and cost efficiency. Choose the platform that fits your infrastructure, implement automated syncing, and verify image integrity through signature validation. With these patterns, you maintain full supply chain visibility while using CleanStart's maintained, certified images.
For Kubernetes-specific deployment patterns, see: ../../06-operate/kubernetes-helm/helm-charts-kubernetes.md
