Skip to main content
This tutorial shows you how to deploy Zot, a container registry built for OCI, on CoreWeave Kubernetes Service (CKS). You’ll store container images in CoreWeave AI Object Storage and use LOTA as the S3 endpoint for faster reads within the cluster. When you finish, you’ll have a private, TLS-secured registry that your CKS workloads can pull from and push to using a public hostname under .coreweave.app.
OCI conformanceCoreWeave has tested that the Zot registry passes the OCI Distribution Specification conformance suite, validating its compliance with the Open Container Initiative standards on the CoreWeave cloud. The conformance suite tests core registry operations including push and pull workflows, content discovery, and content management. This certification confirms that Zot works reliably with OCI-compatible tooling and clients across the container ecosystem.

What you’ll use

This tutorial uses:

What you’ll do

In this tutorial, you:
  1. Install cert-manager to issue a TLS certificate for the registry.
  2. Create registry credentials for authenticating with the registry.
  3. Issue a TLS certificate for the registry hostname.
  4. Deploy Zot using Helm with S3-backed storage through LOTA, a public LoadBalancer Service, and the TLS certificate mounted into the Pod.
  5. Push and pull a container image to verify the registry.

Prerequisites

Before you start, you must have: You’ll need the following tools on your local machine:

Verify cluster access

Before installing anything, confirm that kubectl is configured for the right cluster and that the cluster has the Node types this tutorial requires. Verify that you can access your cluster with kubectl:
You should see something similar to:
Verify your cluster has at least one CPU Node:
You should see at least one Node with cpu in the CLASS column:

Install cert-manager

This tutorial uses the CoreWeave cert-manager Helm chart to issue a TLS certificate for the registry. Add the CoreWeave Helm repository:
Install cert-manager:
Enable the cert-issuers subchart to create Let’s Encrypt ClusterIssuers:
Verify the ClusterIssuers are ready:
You should see:

Determine the registry hostname

You need the registry’s public hostname before you can issue a TLS certificate or push and pull images, so determine it now and reuse it in the steps that follow. CoreWeave’s External Hostname Controller allocates an FQDN under .coreweave.app for any public LoadBalancer Service annotated with service.beta.kubernetes.io/external-hostname. For more details, see Create a public DNS name. Your registry hostname follows this pattern:
Replace:
  • [ORG-ID] with your organization ID.
  • [CLUSTER-NAME] with the name of the target CKS cluster.
For example, if your Org ID is abc123 and your cluster is named prod, your registry hostname is registry.abc123-prod.coreweave.app. Note this hostname for the following steps.
If you already have any public LoadBalancer Service running in the cluster, you can inspect its ExternalRecords status condition to confirm the DNS suffix:

Create a storage bucket

Zot uses an AI Object Storage bucket as its backing store for image layers and manifests. Create an AI Object Storage bucket in the Cloud Console. Note the bucket name for later use.

Generate access credentials

Zot authenticates to the bucket with an S3-compatible Access Key. Create an Access Key for the bucket in the Cloud Console. Save the Access Key ID and Secret Key. You’ll need both when configuring Zot’s storage driver.

Create registry credentials

These credentials control who can push and pull images through the registry itself, separate from the bucket credentials in the previous step. Generate a bcrypt-hashed password for the registry admin user. Replace [USERNAME] and [PASSWORD] with your chosen credentials:
If htpasswd is not installed, install it with apt-get install apache2-utils on Debian/Ubuntu or brew install httpd on macOS.
This tutorial uses htpasswd for simplicity. Zot also supports LDAP, mutual TLS, bearer token (OAuth2), and OpenID Connect with providers like GitHub, Google, and GitLab.
Create a Kubernetes Secret containing the htpasswd file:

Issue a TLS certificate

Create a Certificate resource so cert-manager issues a TLS certificate for your registry hostname using the bundled letsencrypt-prod ClusterIssuer. Save the following as registry-cert.yaml. Replace registry.[ORG-ID]-[CLUSTER-NAME].coreweave.app with the hostname from the previous step:
registry-cert.yaml
Apply the manifest:
Wait for the certificate to be ready. Issuance typically completes within one to two minutes:
You should see:
Confirm the certificate and its backing Secret exist:
The Secret contains tls.crt and tls.key keys, which you’ll mount into the Zot Pod in the next step.

Deploy Zot

With the certificate, bucket, and credentials in place, you can now install Zot itself. The following Helm values wire together the storage bucket through LOTA, the htpasswd Secret for authentication, the cert-manager-issued TLS Secret, and the public LoadBalancer Service annotations. Add the Zot Helm repository:
Create a zot-values.yaml file. Replace the placeholder values with your own:
zot-values.yaml
Replace the following placeholders: Configuration notes:
  • The service block creates a public LoadBalancer Service. The external-hostname: registry annotation instructs the External Hostname Controller to allocate registry.[ORG-ID]-[CLUSTER-NAME].coreweave.app for the Service. The coreweave-load-balancer-type: public annotation provisions a public IP. The Service listens on port 443 and forwards to the container’s zot named port, which is set by httpGet.port (described in the following item).
  • The ingress.enabled: false field disables the chart’s Ingress resource. External clients connect directly to the LoadBalancer Service, so no ingress controller is required.
  • The httpGet block sets the probe scheme to HTTPS and the container port to 5000. The chart uses these values for its startup probe and to name the container port that the Service targets. The scheme must be HTTPS because Zot terminates TLS on this port. Probing with HTTP fails.
  • The extraVolumes and extraVolumeMounts fields mount the registry-cert Secret (created by cert-manager in the previous step) at /tls inside the Zot container. The http.tls block in the Zot config points to tls.crt and tls.key in that directory, so Zot terminates TLS directly.
  • The region must match where your AI Object Storage bucket was created. Find this in the Cloud Console on the bucket details page.
  • The regionendpoint is set to http://cwlota.com, the LOTA endpoint. LOTA caches object storage reads on local Node disks for faster access. The secure field is false because LOTA uses HTTP within the cluster. To access the bucket from outside a CKS cluster, use https://cwobject.com as the endpoint and set secure to true.
  • The compat: ["docker2s2"] field enables Docker v2 manifest support. Without this, docker push fails because Zot rejects Docker v2 manifests by default. Remove this field if your workflow exclusively uses OCI-format images.
Install the chart:
cert-manager renews the registry-cert Secret automatically before expiry. Because the Secret is mounted into the Zot Pod, the updated certificate is available on disk after rotation. Restart the Zot Pod to pick up the renewed certificate if Zot does not reload it automatically.
Verify the Pod is running:
You should see:
Verify the LoadBalancer Service has an external IP and the expected public FQDN:
The EXTERNAL-IP column shows a public IP address, and the second command prints registry.[ORG-ID]-[CLUSTER-NAME].coreweave.app. DNS propagation typically completes within one to two minutes.

Verify the registry

Confirm that the registry is reachable, that authentication works, and that the S3-backed storage driver is wired up correctly by pushing and pulling a small test image. Log in to your registry using Docker. Replace [ORG-ID] and [CLUSTER-NAME] with the values for your cluster, and use the username and password you created in the Create registry credentials step:
When prompted, enter the password from REGISTRY_PASS in the Create registry credentials section (or use -p $REGISTRY_PASS to provide it directly). Push a test image:
You should see the image layers pushed:
Pull the image back to verify:
You should see:

Enable the web UI (optional)

Zot ships with an optional web UI that lets you browse repositories, tags, and image manifests in a browser. The UI is deactivated by default. To turn it on, enable the ui and search Zot extensions in the Zot config. Both are required because the UI depends on the search extension for catalog data. Add the following extensions block to the http object in zot-values.yaml. Place it as a sibling of log:
zot-values.yaml: configFiles.config.json additions
Apply the change with helm upgrade:
Wait for the Pod to roll out:
Open the UI in a browser:
The UI prompts for credentials. Sign in with the admin username and password from the Create registry credentials step.
Retrieving the login passwordZot stores only the bcrypt hash of the password in the registry-htpasswd Secret, so you can’t read the plaintext password back from the cluster. If your shell still has the $REGISTRY_PASS environment variable from the Create registry credentials step, print it with:
If $REGISTRY_PASS is no longer set, use the plaintext password you chose when you generated the htpasswd entry. If you’ve lost the password, rotate it by generating a new htpasswd entry and applying it through helm upgrade:
Update the secretFiles.htpasswd field in zot-values.yaml with the new $HTPASSWD value, then:
Sign in with the new credentials.

What you’ve deployed

You now have a Zot container registry running on CKS with htpasswd authentication, S3-backed storage through LOTA, a Let’s Encrypt TLS certificate issued through a DNS01 challenge, and a public FQDN under the .coreweave.app domain. External clients connect directly to the Zot LoadBalancer Service on port 443. Zot terminates TLS inside the Pod using the mounted registry-cert Secret, and no ingress controller sits in the request path. cert-manager renews the certificate automatically before expiry. To pull images from this registry in your CKS workloads, create an imagePullSecret with the same credentials you used in the Create registry credentials step.

Clean up

If you no longer need the registry, remove it and the supporting resources so they don’t continue to consume cluster and storage resources. To remove the registry and all resources installed in this tutorial:
Uninstalling cert-manager leaves its Custom Resource Definitions (CRDs) in the cluster. To remove them:

Additional resources

Last modified on June 10, 2026