--- title: Install from a private registry description: "Run OME from your own or a mirrored registry: copy its images, set global.hub, and give pods pull credentials with imagePullSecrets." --- Run OME from your own registry: a corporate mirror, or a registry in an air-gapped network. You install OME as in [Install OME](install.md), with two changes: `global.hub` points OME's images at your registry, and a pull Secret gives pods the credentials for it.
- A registry that your nodes can pull from. The examples use `registry.example.com`, with OME's images under `registry.example.com/ome`. - The images in [Images to copy](#images-to-copy), copied into that path. - If Helm can't reach `ghcr.io`, the charts as files. On a machine that can, run `helm pull oci://ghcr.io/moirai-internal/charts/ome-resources --version 1.2.2`, and the same for `ome-crd`. Copy the `.tgz` files across, and give Helm their paths in place of the `oci://` URLs. - cert-manager and the `ome-crd` chart, installed as in the first two steps of [Install OME](install.md). cert-manager has images of its own to copy; `ome-crd` has none.
## Images to copy The chart names OME's own images `/:`, where the hub is `ghcr.io/moirai-internal` by default. Setting `global.hub` moves them all, including the images that OME adds to pods later. Copy them with their names and tags unchanged. A node pulls an image only when a pod runs it, so skip the images of features that you don't use. | Image | Used by | Set with | | --- | --- | --- | | `ghcr.io/moirai-internal/ome-manager:v1.2.2` | The controller | `ome.controller.image`, `ome.controller.tag` | | `ghcr.io/moirai-internal/model-agent:v1.2.2` | The [model agent](../guides/operate-ome/model-agent.md) | `modelAgent.image.repository`, `modelAgent.image.tag` | | `ghcr.io/moirai-internal/ome-agent:v1.2.2` | Serving pods' optional `model-init` and `serving-sidecar` containers, and metadata Jobs for models on PVCs | `ome.omeAgent.image`, `ome.omeAgent.tag` | | `ghcr.io/moirai-internal/genai-bench:0.0.3` | BenchmarkJobs. The chart's default tag has no image, so Step 2 sets `0.0.3` | `ome.benchmarkJob.image`, `ome.benchmarkJob.tag` | | `ghcr.io/moirai-internal/multinode-prober:v1.2.2` | The `MultiNodeRayVLLM` deployment mode, on v1.2.2 only | `ome.multinodeProber.image`, `ome.multinodeProber.tag` | | `docker.io/prom/prometheus:v3.0.1`, copied as `registry.example.com/ome/prometheus:v3.0.1` | The chart's Prometheus, since v1.3. `global.hub` doesn't move it | `prometheus.image.repository`, `prometheus.image.tag` | | Your runtimes' images | Serving containers and routers | Each runner's `image`: see [Serving pods](#serving-pods) | Since v1.3, the [OME scheduler](../guides/operate-ome/ome-scheduler.md) and [Alfred](../guides/scheduling/run-alfred.md), both alpha, and the [quota manager](../guides/operate-ome/accelerator-quota.md) have charts of their own. Each takes its own `global.hub` and `global.imagePullSecrets`. OME's releases don't publish their images, so you build them. ## Step 1: Create a pull Secret Skip this step if your registry allows anonymous pulls, or your nodes already have credentials for it. Create a Secret with your registry's credentials in the namespace `ome`: ```bash kubectl create secret docker-registry ome-registry-cred -n ome \ --docker-server=registry.example.com \ --docker-username= \ --docker-password= ``` ```output secret/ome-registry-cred created ``` Step 4 adds the Secret to your models' namespaces. ## Step 2: Point OME at your registry Add these values to the `values.yaml` from [Install OME](install.md#step-3-install-ome), or create the file with them: ```yaml title="values.yaml" global: hub: registry.example.com/ome imagePullSecrets: - name: ome-registry-cred modelAgent: enabled: true ome: benchmarkJob: tag: "0.0.3" prometheus: image: repository: registry.example.com/ome/prometheus ``` | Value | What it does | | --- | --- | | `global.hub` | The path that holds your copies of OME's images. | | `global.imagePullSecrets` | The pull Secrets of the controller, the model agent and, since v1.3, Prometheus. Leave it out if you skipped Step 1. | | `modelAgent.enabled` | Deploys the model agent, as in [Install OME](install.md#step-3-install-ome). | | `ome.benchmarkJob.tag` | The tag of the genai-bench image that BenchmarkJobs run. | | `prometheus.image.repository` | Your copy of the Prometheus image. Since v1.3. | `ome.controller.imagePullSecrets` and `modelAgent.imagePullSecrets` replace `global.imagePullSecrets` for their own pod. If your values set either one, add `ome-registry-cred` to it. Install or upgrade OME with the values file: === "Helm 4" ```bash helm upgrade --install ome oci://ghcr.io/moirai-internal/charts/ome-resources \ --version 1.2.2 \ --namespace ome \ -f values.yaml \ --server-side=false ``` [Install OME](install.md#step-3-install-ome) explains why OME needs `--server-side=false`. === "Helm 3" ```bash helm upgrade --install ome oci://ghcr.io/moirai-internal/charts/ome-resources \ --version 1.2.2 \ --namespace ome \ -f values.yaml ``` Helm reports the release's status as `deployed`, and on an upgrade the controller and model agent pods restart with images from your registry. Since v1.3, the first install fails with [a webhook error](install.md#the-first-install-fails-with-a-webhook-error): wait, then run the command again. ## Step 3: Check the images List the image references in the manifest that Helm installed: ```bash helm get manifest ome -n ome | grep -E 'image:|"image":' ``` ```output "image": "registry.example.com/ome/multinode-prober:v1.2.2", "image": "registry.example.com/ome/ome-agent:v1.2.2", "image": "registry.example.com/ome/ome-agent:v1.2.2", "image": "registry.example.com/ome/ome-agent:v1.2.2", "image": "registry.example.com/ome/genai-bench:0.0.3", image: registry.example.com/ome/model-agent:v1.2.2 image: registry.example.com/ome/ome-manager:v1.2.2 ``` Every line should name your registry. Since v1.3, the multinode-prober line is gone, and the list ends with the Prometheus image. Then wait for the controller and the model agent to be ready, as in [Step 4 of Install OME](install.md#step-4-verify-the-installation). ## Step 4: Set up your model namespaces OME also runs pods in your models' namespaces: serving pods, BenchmarkJob pods and the metadata Jobs of models on PVCs. Your values give pull secrets only to the pods in `ome`, so if your registry needs credentials, create the Secret in each of these namespaces too. For `qwen3-0-6b`, the namespace of [Serve your first model](serve-your-first-model.md): ```bash kubectl create namespace qwen3-0-6b kubectl create secret docker-registry ome-registry-cred -n qwen3-0-6b \ --docker-server=registry.example.com \ --docker-username= \ --docker-password= ``` ```output namespace/qwen3-0-6b created secret/ome-registry-cred created ``` ### Serving pods First, point your runtimes at your registry. A runtime names its images in every `runner` under its `engineConfig`, `decoderConfig` and `routerConfig`, including those in `leader` and `worker` blocks. An InferenceService can override them. The `ome-serving` chart takes them from `defaults.image` and `defaults.routerImage`, which are `docker.io/lmsysorg/sglang:v0.5.5.post3-cu129-amd64` and `fra.ocir.io/idqj093njucb/smg:v0.2.4.post1-dev` by default: see [ome-serving chart values](../reference/operate-ome/ome-serving-values.md#defaults). Since v1.3, a runtime with the annotation `ome.io/engine: sglang-pd` fills in a runner that has no image with `lmsysorg/sglang:latest` or `lmsysorg/sglang-router:latest` from Docker Hub, so set every runner's image. Then add the Secret to the InferenceService's `engine`, and to its `decoder` and `router` if it runs them. When you create the InferenceService in [Serve your first model](serve-your-first-model.md#step-5-create-the-inferenceservice), add it like this: ```yaml title="qwen3-0-6b-isvc.yaml" apiVersion: ome.io/v1beta1 kind: InferenceService metadata: name: qwen3-0-6b namespace: qwen3-0-6b spec: model: name: qwen3-0-6b runtime: name: srt-qwen3-0-6b engine: minReplicas: 1 maxReplicas: 1 imagePullSecrets: - name: ome-registry-cred ``` To cover every InferenceService that uses a runtime, set `imagePullSecrets` in the runtime's `engineConfig`, `decoderConfig` or `routerConfig` instead. The runtime's top-level `imagePullSecrets` doesn't reach the pods. Engine and decoder pods that have no `imagePullSecrets` get the pull secrets of their ServiceAccount: the namespace's `default`, unless the component sets `serviceAccountName`. Routers run as their own ServiceAccount, named after the InferenceService with `-router` added, so give them `imagePullSecrets`. On v1.2.2, where `leader` and `worker` blocks make a component `MultiNode` (deprecated), set `imagePullSecrets` in those blocks too. If you serve [fine-tuned weights](../concepts/models/fine-tuned-weights.md#configure-the-download), the `fineTunedAdapter` entry that you add to `inferenceservice-config` names the adapter's image. Point its `image` at your registry. ### BenchmarkJob and metadata Job pods {#benchmarkjob-pods} These pods get pull secrets only from their ServiceAccount, so give it the Secret. Check its list of pull secrets first: the first patch below replaces the list. === "BenchmarkJob" The pods of a [BenchmarkJob](../guides/deploy-models/run-benchmarks.md) run as the namespace's `default` ServiceAccount, and so does the multinode prober on v1.2.2. Check its pull secrets: ```bash kubectl get serviceaccount default -n qwen3-0-6b -o jsonpath='{.imagePullSecrets}' ``` If the output is empty, give the ServiceAccount the Secret: ```bash kubectl patch serviceaccount default -n qwen3-0-6b \ -p '{"imagePullSecrets": [{"name": "ome-registry-cred"}]}' ``` ```output serviceaccount/default patched ``` When it prints a list without `ome-registry-cred`, add the Secret to the list instead, so that other pods that run as `default` keep theirs: ```bash kubectl patch serviceaccount default -n qwen3-0-6b --type=json \ -p '[{"op": "add", "path": "/imagePullSecrets/-", "value": {"name": "ome-registry-cred"}}]' ``` ```output serviceaccount/default patched ``` === "Metadata Job" The metadata Job of a [model on a PVC](../guides/deploy-models/serve-models-from-pvc.md) runs in the PVC's namespace, as the ServiceAccount `ome-model-metadata`. OME creates it without pull secrets when it's missing, and uses an existing one as it is, so create it yourself first: ```bash kubectl create serviceaccount ome-model-metadata -n qwen3-0-6b ``` ```output serviceaccount/ome-model-metadata created ``` If it already exists, the command fails with an `already exists` error. Check its pull secrets: ```bash kubectl get serviceaccount ome-model-metadata -n qwen3-0-6b -o jsonpath='{.imagePullSecrets}' ``` If the output is empty, give the ServiceAccount the Secret: ```bash kubectl patch serviceaccount ome-model-metadata -n qwen3-0-6b \ -p '{"imagePullSecrets": [{"name": "ome-registry-cred"}]}' ``` ```output serviceaccount/ome-model-metadata patched ``` When it prints a list without `ome-registry-cred`, add the Secret to the list instead: ```bash kubectl patch serviceaccount ome-model-metadata -n qwen3-0-6b --type=json \ -p '[{"op": "add", "path": "/imagePullSecrets/-", "value": {"name": "ome-registry-cred"}}]' ``` ```output serviceaccount/ome-model-metadata patched ``` To check, run the tab's `kubectl get` command again: the list now includes `ome-registry-cred`. ## Override a single image Add these values to `values.yaml`, and run the command from Step 2 again. A repository that contains a `/` is used as written, without `global.hub`: ```yaml ome: controller: image: registry.example.com/custom/ome-manager tag: v1.2.2-patched ``` `modelAgent.image.hub` replaces `global.hub` for the model agent only. A BenchmarkJob that sets `spec.podOverride.image` runs that image instead of the chart's, so point it at your registry too. Since v1.3, the upgrade restarts the controller when its ConfigMaps change. On v1.2.2, after you change `ome.omeAgent.image` or `ome.omeAgent.tag` but not the controller's image, restart the controller so that metadata Jobs get the new image: ```bash kubectl rollout restart deployment/ome-controller-manager -n ome ``` ```output deployment.apps/ome-controller-manager restarted ``` ## Troubleshooting A pod that can't pull an image stays in `ErrImagePull` or `ImagePullBackOff`. Run `kubectl describe pod` to see the container that's waiting: the pod's events give the image reference and the registry's answer. ### A pod pulls from the old registry Fix the image by container: - The controller or the model agent: run [Step 2](#step-2-point-ome-at-your-registry) again, and check the result with [Step 3](#step-3-check-the-images). - `model-init` or `serving-sidecar`: the pod predates your change. Delete it, and its replacement gets the new image. - `fine-tuned-adapter`: fix the `fineTunedAdapter` entry as in [Serving pods](#serving-pods), then delete the pod. - The serving container or the router: point the runner images at your registry, as in [Serving pods](#serving-pods). ### The Prometheus pod pulls from Docker Hub {since=v1.3} `global.hub` doesn't move the Prometheus image. Set `prometheus.image.repository` to your copy, as in [Step 2](#step-2-point-ome-at-your-registry), or set `prometheus.enabled` to `false` if you run your own Prometheus. ### The image isn't found The path or the tag in the reference doesn't match your copy. Copy the image under that name, or fix the value that sets it: see [Images to copy](#images-to-copy). ### The registry refuses the pull The pod has no Secret with credentials that the registry accepts: - Pods in `ome`: check that `ome-registry-cred` is in `ome` with the right credentials, and that the pod's pull secrets name it, as in [Step 2](#step-2-point-ome-at-your-registry). - Serving pods: add the Secret as in [Serving pods](#serving-pods). - BenchmarkJob and metadata Job pods: give the ServiceAccount the Secret, as in [BenchmarkJob and metadata Job pods](#benchmarkjob-pods). A pod gets its ServiceAccount's pull secrets only when it's created, so then replace the pod. Delete a metadata Job's pod, and the Job creates another. A BenchmarkJob's Job doesn't retry a failed pod, so delete the Job, and OME creates a new Job and pod. ## Next steps - [Serve your first model](serve-your-first-model.md): point its runtime's image at your copy, and add the Secret to its InferenceService as in [Serving pods](#serving-pods). Skip its `kubectl create namespace`, because Step 4 created the namespace. - [Pre-configured models and runtimes](pre-configured-models.md): install models and runtimes from OME's catalog, and point their images at your registry with the chart's values.