Skip to content

Container Registry

Eric Mann edited this page Jan 7, 2026 · 2 revisions

Container Registry

Displace includes in-cluster container registries to simplify the development workflow. Instead of pushing images to external registries like Docker Hub, you can push directly to your cluster's built-in registry.

Overview

The Problem

Traditional Kubernetes development requires:

  1. Build Docker images locally
  2. Push to an external registry (Docker Hub, ECR, GCR, etc.)
  3. Configure image pull secrets in Kubernetes
  4. Reference external registry URLs in manifests

This creates friction, especially for local development where images never leave your machine.

The Solution

Displace provides in-cluster container registries:

  • Local clusters (k3d): Dedicated k3d registry, zero configuration
  • Remote clusters: In-cluster Docker registry, automatically deployed
  • Simple CLI: displace registry push <image> works everywhere

Registry Architecture

Local Development (k3d)

flowchart TB
    subgraph local["Local Development Environment"]
        docker["Docker Client"]
        registry["k3d-displace-registry<br/>(localhost:5000)"]
        subgraph cluster["k3d Cluster (displace-local)"]
            pods["Application Pods<br/>Pull from: k3d-displace-registry:5000"]
        end
    end

    docker -->|"docker push"| registry
    registry -->|"k3d network"| pods
Loading

Key Features:

  • Registry created automatically during displace install
  • Images pushed via Docker CLI to localhost:5000
  • Pods pull from k3d-displace-registry:5000
  • Data persists across cluster restarts

Remote Clusters (AWS/GCP/Azure/DO)

flowchart TB
    subgraph local["Local Machine"]
        client["Docker Client"]
    end

    subgraph remote["Remote Kubernetes Cluster"]
        ingress["registry.cluster.local:5000<br/>(Ingress/Port-forward)"]
        subgraph registry_pod["displace-registry"]
            registry["Container Registry<br/>PVC: 20Gi"]
        end
        pods["Application Pods"]
    end

    client -->|"docker push<br/>(via tunnel)"| ingress
    ingress --> registry
    registry -->|"Image Pull"| pods
Loading

Key Features:

  • Registry deployed during cluster bootstrap
  • Basic auth for security
  • PersistentVolumeClaim for storage (20Gi default)
  • Internal ClusterIP for pod pulls

Registry Commands

Check Registry Status

View registry configuration and health:

displace registry status

Example output:

Registry Status: k3d-displace-registry
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Type:      k3d (local)
Endpoint:  k3d-displace-registry:5000
Port:      5000
Status:    ✓ running
Images:    12

To push images:
  displace registry push <image>

Or manually:
  docker tag <image> localhost:5000/<image>
  docker push localhost:5000/<image>

Push Images

Push a local Docker image to the cluster's registry:

# Push with default tag
displace registry push myapp

# Push with specific tag
displace registry push myapp:v1.0.0

# Push to specific cluster's registry
displace registry push myapp --cluster my-cluster

Example output:

Pushing myapp:latest to registry: k3d-displace-registry
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Tagging image for localhost:5000...
Pushing to localhost:5000/myapp:latest...

✓ Successfully pushed myapp:latest

Image available at:
  From host:    localhost:5000/myapp:latest
  From cluster: k3d-displace-registry:5000/myapp:latest

List Images

View all images in the cluster's registry:

displace registry list

Example output:

Images in registry: k3d-displace-registry
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
REPOSITORY              TAGS
myapp                   latest, v1.0.0, v1.0.1
wordpress               6.4, latest
laravel-app             dev, main

Create Registry

Create a new local k3d registry:

# Create with default name
displace registry create

# Create with custom name
displace registry create my-registry

# Create on specific port
displace registry create --port 5001

Delete Registry

Remove a local registry:

# Delete with confirmation
displace registry delete displace-registry

# Force delete without confirmation
displace registry delete displace-registry --force

Using the Registry

With Displace Templates

Templates are pre-configured to use the cluster registry. The Makefile targets handle image building and pushing:

# Build and push in one command
make build
make push

# Or combined
make build push deploy

Manual Usage

Build and push images manually:

# Build your image
docker build -t myapp:latest .

# Push to cluster registry
displace registry push myapp:latest

# Reference in Kubernetes manifests
# Use: k3d-displace-registry:5000/myapp:latest

In Kubernetes Manifests

Reference images from the cluster registry:

# For local k3d clusters
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - name: app
        image: k3d-displace-registry:5000/myapp:latest
        imagePullPolicy: Always

Registry Setup

Automatic Setup (Recommended)

The registry is created automatically during Displace installation:

# Install Displace (creates registry automatically)
displace install

For new clusters, the registry is linked during creation:

# Registry automatically available for new clusters
displace cluster create my-cluster --provider local

Manual Setup

If you need to create a registry manually:

# Create the registry
displace registry create displace-registry --port 5000

# Verify it's running
displace registry status

To use with an existing k3d cluster:

# Create cluster with registry
k3d cluster create my-cluster --registry-use k3d-displace-registry:5000

Storage and Persistence

Local Registry (k3d)

  • Storage: Docker volume k3d-displace-registry
  • Persistence: Survives cluster deletion/recreation
  • Location: Managed by Docker
  • Cleanup: Only cleared when registry is explicitly deleted

Remote Registry

  • Storage: Kubernetes PersistentVolumeClaim
  • Default Size: 20Gi
  • StorageClass: Cluster default (gp2/gp3 on AWS, pd-standard on GCP)
  • Persistence: Data retained with cluster

Security

Local Clusters

  • Authentication: None required (localhost only)
  • Network: k3d network isolation prevents external access
  • TLS: Not required for localhost

Remote Clusters

  • Authentication: Basic auth with auto-generated credentials
  • Credentials: Stored in Kubernetes Secret
  • TLS: Via cluster Ingress with cert-manager
  • Access: Internal ClusterIP, external via Ingress

Upgrading Existing Clusters

If you have an existing cluster without a registry, you can add one:

For Local k3d Clusters

# Check if registry exists
displace registry status

# If no registry, create one
displace registry create displace-registry

# Recreate cluster with registry
k3d cluster delete displace-local
k3d cluster create displace-local --registry-use k3d-displace-registry:5000

Registry Upgrade Detection

When connecting to an existing cluster, Displace checks for registry support. If your cluster was created before registry support was added, you may see:

⚠️  Registry not detected in cluster 'my-cluster'

This cluster was created before registry support was added.
To enable the registry:

  Option 1: Recreate cluster (recommended for local)
    k3d cluster delete my-cluster
    displace cluster create my-cluster --provider local

  Option 2: Add registry manually
    displace registry create
    # Then update cluster to use registry

Would you like to recreate the cluster now? [y/N]

Options:

  1. Recreate cluster - Best for local development, fresh start with registry
  2. Continue without registry - Use external registry instead
  3. Manual setup - Add registry and reconfigure cluster

Troubleshooting

Registry Not Running

# Check registry status
displace registry status

# Check Docker containers
docker ps | grep registry

# Restart registry if needed
docker restart k3d-displace-registry

Push Fails

# Verify registry is accessible
curl http://localhost:5000/v2/_catalog

# Check Docker can reach registry
docker pull localhost:5000/myapp:latest

# Verify image exists locally
docker images | grep myapp

Pod Can't Pull Image

# Check pod events
kubectl describe pod <pod-name>

# Verify image reference uses correct registry
# Should be: k3d-displace-registry:5000/myapp:latest
# NOT: localhost:5000/myapp:latest (localhost != pod's localhost)

# Check registry is accessible from pod
kubectl run test --rm -it --image=busybox -- wget -qO- http://k3d-displace-registry:5000/v2/_catalog

Image Not Found in Registry

# List all images
displace registry list

# Check specific image
curl http://localhost:5000/v2/myapp/tags/list

# Re-push image
displace registry push myapp:latest

Best Practices

Image Tagging

# Use semantic versioning
displace registry push myapp:v1.0.0

# Use git SHA for builds
displace registry push myapp:$(git rev-parse --short HEAD)

# Always push :latest for convenience
displace registry push myapp:latest

Registry Cleanup

Images accumulate over time. Clean up periodically:

# List images to see what's stored
displace registry list

# For thorough cleanup, recreate registry
displace registry delete displace-registry --force
displace registry create displace-registry

CI/CD Integration

For automated pipelines:

# Build with CI-specific tag
docker build -t myapp:${CI_COMMIT_SHA} .

# Push to registry
displace registry push myapp:${CI_COMMIT_SHA}

# Update deployment
kubectl set image deployment/myapp app=k3d-displace-registry:5000/myapp:${CI_COMMIT_SHA}

Comparison with External Registries

Feature Cluster Registry Docker Hub ECR/GCR
Cost Free Free tier limited Pay per use
Setup Automatic Account required Cloud setup
Authentication Local: none Required IAM integration
Speed Fastest (local) Network dependent Network dependent
Persistence With cluster Permanent Permanent
Best for Development Public images Production

Related Documentation:

Clone this wiki locally