Container Registry
LoKO includes a built-in OCI container registry (Zot) for local image storage and mirroring.
Registry URL
Section titled “Registry URL”The registry is accessible at:
https://cr.${DOMAIN}Example: https://cr.dev.me
Registry web UI (Explore page):
https://cr.${DOMAIN}/exploreExample: https://cr.dev.me/explore
Basic Usage
Section titled “Basic Usage”Push Images
Section titled “Push Images”# Tag image for local registrydocker tag myapp:latest cr.dev.me/myapp:latest
# Push to registrydocker push cr.dev.me/myapp:latestPull Images
Section titled “Pull Images”# Pull from registrydocker pull cr.dev.me/myapp:latestUse in Kubernetes
Section titled “Use in Kubernetes”apiVersion: apps/v1kind: Deploymentmetadata: name: myappspec: template: spec: containers: - name: myapp image: cr.dev.me/myapp:latestNo image pull secrets needed - registry runs in the cluster.
Registry Configuration
Section titled “Registry Configuration”Basic Settings
Section titled “Basic Settings”registry: enabled: true name: cr # Subdomain name storage: 20Gi # PVC size # renovate: datasource=helm depName=zot repositoryUrl=http://zotregistry.dev/helm-charts version: "0.1.95" # Zot chart/app versionStorage Configuration
Section titled “Storage Configuration”Default storage: 20Gi PersistentVolumeClaim
Adjust for your needs:
registry: storage: 50Gi # Larger storageRegistry Mirroring
Section titled “Registry Mirroring”Cache external registries locally for faster pulls:
registry: enabled: true mirroring: enabled: true sources: - name: docker_hub enabled: true - name: quay enabled: true - name: ghcr enabled: true - name: k8s_registry enabled: true - name: mcr enabled: trueCVE Scanning
Section titled “CVE Scanning”Enable Zot CVE scanning (Trivy-backed) from config:
registry: scanning: enabled: true interval: 24henabled: togglesextensions.search.cvein Zot config.interval: Trivy DB update interval (12h,24h, etc.).
How Mirroring Works
Section titled “How Mirroring Works”%% title: Registry Mirroring Flow %%
graph LR
K8s[Kubernetes] -->|1. Pull nginx| Registry[Local Registry]
Registry -->|2. Cache Miss| Docker[Docker Hub]
Docker -->|3. Return Image| Registry
Registry -->|4. Cache & Serve| K8s
K8s -->|5. Future Pulls| Registry
Registry -.Fast local cache.-> K8s
classDef k8sStyle fill:#3949ab,stroke:#283593,color:#fff
classDef registryStyle fill:#00897b,stroke:#00695c,color:#fff
classDef externalStyle fill:#fb8c00,stroke:#e65100,color:#fff
class K8s k8sStyle
class Registry registryStyle
class Docker externalStyle
Using Mirrored Images
Section titled “Using Mirrored Images”Images are automatically cached when pulled:
# In Kubernetes manifestspec: containers: - name: nginx image: docker.io/library/nginx:latestFirst pull: fetches from Docker Hub Subsequent pulls: served from local cache
Registry Operations
Section titled “Registry Operations”Check Registry Status
Section titled “Check Registry Status”loko registry statusShows:
- Registry version
- Storage capacity
- Number of repositories
- Configuration
List Repositories
Section titled “List Repositories”loko registry list-reposOutput:
Local Repositories:- myapp (2 tags)- testimage (1 tag)
Mirrored Repositories:- docker.io/library/nginx (3 tags)- docker.io/library/postgres (2 tags)Show Repository Details
Section titled “Show Repository Details”loko registry show-repo myapploko registry show-repo docker.io/library/nginxList Tags
Section titled “List Tags”loko registry list-tags myappOutput:
Tags for myapp:- latest- v1.0.0- v0.9.0Delete All Tags from a Repository
Section titled “Delete All Tags from a Repository”Remove all tags from a specific repository (marks manifests for garbage collection):
loko registry purge-repo myapploko registry purge-repo myapp --force # skip confirmationPurge Entire Registry
Section titled “Purge Entire Registry”Delete all tags from every repository in the registry:
loko registry purgeloko registry purge --force # skip confirmationLoad a Locally Built Image (Kind)
Section titled “Load a Locally Built Image (Kind)”Load a locally built Docker image directly into the Kind cluster nodes without pushing to the registry:
# Load a single imageloko registry load-image myapp:latest
# Load multiple images at onceloko registry load-image myapp:latest myapp:v1.0.0
# Load into specific nodes onlyloko registry load-image myapp:latest --nodes kind-workerThis calls kind load docker-image under the hood and is useful during development when you want to test a local build without a push/pull cycle.
Building and Pushing
Section titled “Building and Pushing”Build and Push Workflow
Section titled “Build and Push Workflow”# Build imagedocker build -t myapp:v1.0.0 .
# Tag for registrydocker tag myapp:v1.0.0 cr.dev.me/myapp:v1.0.0docker tag myapp:v1.0.0 cr.dev.me/myapp:latest
# Push to registrydocker push cr.dev.me/myapp:v1.0.0docker push cr.dev.me/myapp:latestMulti-Arch Images
Section titled “Multi-Arch Images”# Build multi-archdocker buildx build --platform linux/amd64,linux/arm64 \ -t cr.dev.me/myapp:latest \ --push .Registry in CI/CD
Section titled “Registry in CI/CD”GitHub Actions
Section titled “GitHub Actions”name: Build and Push
on: push
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Build and push run: | docker build -t cr.dev.me/myapp:${{ github.sha }} . docker push cr.dev.me/myapp:${{ github.sha }}Local Build Scripts
Section titled “Local Build Scripts”#!/bin/bash# build-and-deploy.sh
VERSION=$(git rev-parse --short HEAD)IMAGE="cr.dev.me/myapp:$VERSION"
# Builddocker build -t $IMAGE .
# Pushdocker push $IMAGE
# Deploykubectl set image deployment/myapp myapp=$IMAGERegistry Authentication
Section titled “Registry Authentication”No Authentication Required
Section titled “No Authentication Required”By default, the registry allows anonymous:
- ✅ Push
- ✅ Pull
- ✅ List
Suitable for local development.
Enable Authentication (Advanced)
Section titled “Enable Authentication (Advanced)”Edit registry configuration:
# Custom values for registrycomponents: registry: values: httpAuth: enabled: true credentials: - user: admin password: secretpasswordThen configure Docker:
docker login cr.dev.meRegistry as Build Cache
Section titled “Registry as Build Cache”Use registry for Docker build cache:
# Build with cache from registrydocker build \ --cache-from cr.dev.me/myapp:latest \ -t cr.dev.me/myapp:new \ .
# Push with cachedocker build \ --cache-to type=registry,ref=cr.dev.me/myapp:buildcache \ -t cr.dev.me/myapp:latest \ .Registry Web UI
Section titled “Registry Web UI”Zot includes a web interface:
https://cr.dev.meFeatures:
- Browse repositories
- View tags
- Search images
- View image details
- Delete tags (if authentication enabled)
Troubleshooting
Section titled “Troubleshooting”Push Fails with TLS Error
Section titled “Push Fails with TLS Error”# Check certificate trustdocker info | grep -i insecure
# Verify registry certificatecurl -v https://cr.dev.me/v2/
# Reinstall CA trust and Docker certs.dloko certs ca installRegistry Not Accessible
Section titled “Registry Not Accessible”# Check registry podkubectl get pods -n kube-system -l app=zot
# Check registry servicekubectl get svc -n kube-system zot
# Check registry logsloko logs workload zotDisk Space Full
Section titled “Disk Space Full”# Check registry storagekubectl get pvc -n kube-system
# Increase storage sizevim loko.yaml# Update components.registry.storage.size
# Recreate environmentloko env recreateMirroring Not Working
Section titled “Mirroring Not Working”# Verify mirroring configyq '.environment.components.registry.mirroring' loko.yaml
# Check registry logs for errorsloko logs workload zot | grep -i mirror
# Test manual pulldocker pull cr.dev.me/docker.io/library/nginx:latestCannot Pull from Registry in Kubernetes
Section titled “Cannot Pull from Registry in Kubernetes”# Check node containerd configdocker exec -it dev-me-worker cat /etc/containerd/config.toml
# Verify registry is in hosts.tomldocker exec -it dev-me-worker cat /etc/containerd/certs.d/cr.dev.me/hosts.toml
# Recreate clusterloko env recreateAdvanced Configuration
Section titled “Advanced Configuration”Custom Registry Port
Section titled “Custom Registry Port”components: registry: port: 5000 # Custom portGarbage Collection
Section titled “Garbage Collection”Enable automatic garbage collection:
components: registry: values: gc: enabled: true interval: "24h" deleteUntagged: trueStorage Backend
Section titled “Storage Backend”Use different storage backend:
components: registry: values: storage: type: s3 s3: region: us-east-1 bucket: my-registry-bucketImage Scanning
Section titled “Image Scanning”Enable vulnerability scanning:
components: registry: values: extensions: search: enable: true cve: updateInterval: "24h"Registry Performance
Section titled “Registry Performance”Cache Hit Rate
Section titled “Cache Hit Rate”Monitor cache efficiency:
# Check mirrored repositoriesloko registry list-repos | grep docker.io
# Compare with pulled imageskubectl get pods -A -o yaml | grep image:Storage Usage
Section titled “Storage Usage”# Check PVC usagekubectl get pvc -n kube-system zot-storage
# Describe PVC for detailskubectl describe pvc -n kube-system zot-storageBest Practices
Section titled “Best Practices”- Use semantic versioning for tags (
v1.0.0) - Tag with both version and
latest - Enable mirroring for frequently used images
- Monitor storage usage
- Clean up old tags regularly
❌ Don’t
Section titled “❌ Don’t”- Use
:latesttag only (hard to track versions) - Store large unused images (wastes space)
- Disable TLS verification (security risk)
- Forget to push after building
Next Steps
Section titled “Next Steps”- Workload Management - Deploy applications
- Certificates - TLS certificate setup
- Troubleshooting - Registry issues