Setting up a Docker Registry backed with Cloudflare R2

Published at 07 May 2025

You can run a private Docker registry using Cloudflare R2 as your storage backend, in this tutorial we're going to configure it in a Kubernetes cluster.

Creating bucket and access keys

First, we need to create a R2 bucket where we'll store the Docker images. We also need an API access key and secret key to access the bucket.

Once we have created the bucket and have both keys, we create a secret.

vi registry-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: r2-secret
type: Opaque
data:
  ACCESS_KEY_ID: <your access key in base64>
  ACCESS_SECRET_KEY: <your secret key in base64>

And we apply the configuration.

kubectl apply -f registry-secrets.yaml

Deploying the Registry

Once we've applied the secrets, we create the Registry deployment.

vi registry-deployment.yaml

Here is an example of the deployment, you should change some values to make it work in your cluster.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: r2-registry
spec:
  replicas: 1
  selector:
    matchLabels:
      app: r2-registry
  template:
    metadata:
      labels:
        app: r2-registry
    spec:
      restartPolicy: Always
      containers:
      - name: registry
        image: registry:2
        env:
        - name: REGISTRY_HEALTH_STORAGEDRIVER_ENABLED
          value: "false"
        - name: REGISTRY_STORAGE_DELETE_ENABLED
          value: "true"
        - name: REGISTRY_STORAGE
          value: s3
        - name: REGISTRY_STORAGE_S3_REGION
          value: auto
        - name: REGISTRY_STORAGE_S3_REGIONENDPOINT
          value: https://<account id>.r2.cloudflarestorage.com
        - name: REGISTRY_STORAGE_S3_ENCRYPT
          value: "false"
        - name: REGISTRY_STORAGE_S3_SECURE
          value: "true"
        - name: REGISTRY_STORAGE_S3_CHUNKSIZE
          value: "104857600"
        - name: REGISTRY_STORAGE_S3_BUCKET
          value: <bucket name>
        - name: REGISTRY_STORAGE_S3_ACCESSKEY
          valueFrom:
            secretKeyRef:
              name: r2-secret
              key: ACCESS_KEY_ID
        - name: REGISTRY_STORAGE_S3_SECRETKEY
          valueFrom:
            secretKeyRef:
              name: r2-secret
              key: ACCESS_SECRET_KEY

Once we've modified the deployment file, we apply it.

kubectl apply -f registry-deployment.yaml

Create service and ingress

To access the registry, we might want to create a service and an ingress resource.

The registry exposes at port 5000, so we create a service to that port.

vi registry-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: r2-registry
spec:
  type: ClusterIP
  ports:
  - port: 5000
    targetPort: 5000
    protocol: TCP
  selector:
    app: r2-registry

And we apply it.

kubectl apply -f registry-service.yaml

Next, we create an ingress resource so we can access the registry using our own domain or subdomain.

vi registry-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: r2-registry
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
spec:
  rules:
  - host: registry.binarycomet.net
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: r2-registry
            port:
              number: 5000

And we apply it.

kubectl apply -f registry-ingress.yaml

Next steps

Now we have a working Docker registry that can be accessed via our own domain or subdomain, but it's not production-ready yet.

You should add authentication so only the users authorized can access the registry.

Also, it might be interesting to create an SSL certificate, so we don't have to register it as an insecure-registries everywhere we want to use it.