Skip to main content
CleanStart

Knowledge Hub

Mirroring CleanStart Images to Your Private Registry

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=true

Step 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-1

Step 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-1

Step 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: 8080

Alternative: 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/EKSECRPullRole

Step 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 UTC

Azure 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 true

Step 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.12

Step 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:20

Register the task:

az acr task create \  --registry myorgacr \  --name sync-cleanstart-images \  --file acr-import-task.yaml \  --schedule "0 2 * * *"  # Daily at 2 AM

Step 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_ID

In 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: 8080

Step 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 myorgacr

Once 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/cleanstart

Step 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.com

In 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: 8080

Harbor (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 credentials

In the Harbor UI:

  1. Create a new project: cleanstart
  2. Set access level: "Public" (so Kubernetes nodes can pull without secrets)
  3. 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/policies

Or 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/report

Step 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/robots

Use 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:latest

JFrog 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-remote

Step 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/cleanstart

Now, pulling from artifactory.example.com/cleanstart/base:latest will:

  1. Check the local repository
  2. If not found, pull from cleanstart-remote (registry.cleanstart.com)
  3. 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/policies

Nexus 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/proxy

Step 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/hosted

Step 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/group

Step 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-policies

Assign 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.dev

After 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:latest

Enforce 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-system

Option 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_failure

Namespace 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