Webux Lab - Blog
Webux Lab Logo

Webux Lab

By Studio Webux

Search

By Tommy Gingras

Last update 2026-02-27

K3sDocker

K3s Private Docker Registry

Self-hosted Docker registry on K3s using the official registry:2 image, secured with htpasswd authentication, a TLS certificate via cert-manager, and restricted to the WireGuard VPN and internal cluster networks via a Traefik IP allowlist.

Prerequisites

  • K3s running with Traefik and cert-manager configured (see the K3s Setup article)
  • A cloudflare-cluster-issuer ClusterIssuer

1. Create Auth Secret

kubectl create namespace registry

# Install htpasswd if needed
sudo apt install apache2-utils  # Debian/Ubuntu
# or: sudo dnf install httpd-tools  # Rocky Linux

# Generate bcrypt credentials
htpasswd -Bc /tmp/htpasswd admin

# Create the secret
kubectl create secret generic registry-auth \
  --from-file=htpasswd=/tmp/htpasswd \
  -n registry

2. Deploy Registry

The registry uses a 50 Gi PVC for image storage and mounts the htpasswd secret for authentication.

registry.yaml

---
apiVersion: v1
kind: Namespace
metadata:
  name: registry
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: registry-pvc
  namespace: registry
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: registry
  namespace: registry
spec:
  replicas: 1
  selector:
    matchLabels:
      app: registry
  template:
    metadata:
      labels:
        app: registry
    spec:
      containers:
        - name: registry
          image: registry:2
          ports:
            - containerPort: 5000
          env:
            - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY
              value: /data
            - name: REGISTRY_AUTH
              value: htpasswd
            - name: REGISTRY_AUTH_HTPASSWD_REALM
              value: Registry
            - name: REGISTRY_AUTH_HTPASSWD_PATH
              value: /auth/htpasswd
          volumeMounts:
            - name: storage
              mountPath: /data
            - name: auth
              mountPath: /auth
      volumes:
        - name: storage
          persistentVolumeClaim:
            claimName: registry-pvc
        - name: auth
          secret:
            secretName: registry-auth
---
apiVersion: v1
kind: Service
metadata:
  name: registry
  namespace: registry
spec:
  selector:
    app: registry
  ports:
    - port: 5000
      targetPort: 5000
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: registry-cert
  namespace: registry
spec:
  secretName: registry-tls
  issuerRef:
    name: cloudflare-cluster-issuer
    kind: ClusterIssuer
  dnsNames:
    - registry.webux.dev
---
# Restrict access to WireGuard + k3s pod/service networks
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: registry-allowlist
  namespace: registry
spec:
  ipAllowList:
    sourceRange:
      - 10.11.0.0/24    # WireGuard
      - 10.42.0.0/16    # k3s pod CIDR
      - 10.43.0.0/16    # k3s service CIDR
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: registry
  namespace: registry
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`registry.webux.dev`)
      kind: Rule
      middlewares:
        - name: registry-allowlist
          namespace: registry
      services:
        - name: registry
          port: 5000
  tls:
    secretName: registry-tls
kubectl apply -f registry.yaml

# Watch certificate get issued (~1-2 min)
kubectl get certificate -n registry -w

3. Configure K3s to Trust the Registry

Create registries.yaml with your credentials and copy it to every node. K3s reads this file on startup and generates the containerd hosts.toml internally — do not place hosts.toml manually, it gets wiped on restart.

registries.yaml

mirrors:
  "registry.webux.dev":
    endpoint:
      - "https://registry.webux.dev"

configs:
  "registry.webux.dev":
    auth:
      username: admin
      password: YOUR_REGISTRY_PASSWORD
    tls:
      insecure_skip_verify: false
sudo cp registries.yaml /etc/rancher/k3s/registries.yaml
sudo systemctl restart k3s

# Verify
sudo k3s crictl info | jq '.config.registry'

4. Push Images

# Login
docker login registry.webux.dev

# Tag and push
docker build -t registry.webux.dev/my-app:latest .
docker push registry.webux.dev/my-app:latest

5. Use in Kubernetes Manifests

No imagePullSecret needed — k3s handles auth transparently via registries.yaml.

containers:
  - name: my-app
    image: registry.webux.dev/my-app:latest