Skip to content

Google Artifact Registry Setup for RunWhen Self-Hosted

This guide is for platform engineering and GCP teams standing up Google Artifact Registry (GAR) as the enterprise registry that will host RunWhen images before the Self-Hosted RunWhen Platform install. It is the native-GCP companion to the JFrog Artifactory setup guide — the same data-flow shape, but using GAR’s remote repository feature instead of JFrog remotes and virtual repos.

Prerequisites — read first

  • Self-hosted entitlement is required. RunWhen does not publish platform images to a public registry. Contact support@runwhen.com to request access — onboarding delivers a dedicated repository, a per-customer GCP service-account key, and the version-pinned image inventory.
  • GCP project owner (or Artifact Registry Administrator) on the project that will own these repositories.
  • Outbound HTTPS (443) from your GAR project to the upstream registries listed below — for the initial cache fill, or for ongoing pull-through caching. (Air-gap: pre-load images offline; see Air-gap notes.)
  • The Remote repositories feature must be enabled on your GAR project (available on standard GAR; not available on the legacy GCR gcr.io path).

How RunWhen flows through Google Artifact Registry

Google Artifact Registry data flow: upstream registries proxy through GAR Docker remote repos in the customer project, and serve images to the RunWhen Platform Kubernetes cluster via a single GCP pull secret. The Helm chart OCI artifact rides the same path.

The shape mirrors the JFrog guide, with one key difference: GAR has no virtual aggregator. Each remote repository is its own path prefix, so cluster pulls point at one repo path per upstream rather than a single virtual host path.

  1. One Docker remote repo per upstream registry in your GAR project. RunWhen images get a dedicated, authenticated remote; everything else uses anonymous remotes.
  2. No virtual aggregator — each remote repo resolves to a distinct path under your GAR hostname. Helm values route each image to the correct repo path (Pattern 2 in the chart’s registry routing).
  3. One Kubernetes pull secret authenticates against your GAR host using a read-scoped GCP service account. The chart names it gcr-pull-secret by default — appropriate here, since this is a GCR-compatible registry.
  4. Helm values route each image to the correct repo path. RunWhen ships a worked overlay for GAR (values-example-airgap-gar.yaml) in your onboarding pack.

The Helm chart itself is an OCI artifact in the same RunWhen repository as the images, so it rides through the same remote repo — no second credential.


1. Pick the registry mode

ModeRemote reposVirtual aggregationRecommended for
GAR with remote repos (this guide)YesNo (per-repo paths)GCP-native customers wanting caching, audit, and single-SA access control
Pull directly from RunWhen GARNoNoSmallest footprint — cluster pulls us-docker.pkg.dev directly with the RunWhen-issued SA (no proxy)

Recommendation: Use GAR remote repos (this guide) when you need pull-through caching, an audit trail in your own project, or a single SA boundary. If your cluster can reach us-docker.pkg.dev directly and you don’t need caching, the direct-pull mode skips this guide entirely — just wire the RunWhen-issued SA into the pull secret (Step 6) and point Helm values at the upstream paths as-is.


2. Whitelist upstream FQDNs from your GAR project

Your GAR project’s egress (or your offline mirror) needs to reach these upstreams. After setup, only GAR talks to them — cluster pods talk only to GAR.

FQDNRequired?What it serves
us-docker.pkg.devYesRunWhen platform images + Helm chart OCI artifact
ghcr.ioYesMCP server, code-collection catalog, cortex-tenant, codecollections, LiteLLM, Spilo
registry-1.docker.ioYesSubchart images: Vault, Redis, Mimir, Qdrant, Neo4j, SeaweedFS
charts.bitnami.comHelm onlyRedis / PostgreSQL subcharts
helm.neo4j.comHelm onlyNeo4j subchart
helm.releases.hashicorp.comHelm onlyVault subchart
qdrant.github.ioHelm onlyQdrant subchart
seaweedfs.github.ioHelm onlySeaweedFS subchart
quay.ioOptionalcert-manager images (if proxied through GAR)
registry.k8s.ioOptionalingress-nginx, external-dns (if proxied)

3. Create the Docker remote repositories

Create one GAR remote repo per upstream using the Docker format with Remote repository mode. Repo names below drive pull paths under your GAR hostname — keep them exactly as shown unless you have a strong reason to deviate (RunWhen’s example values overlay assumes these names).

Repo nameUpstream registryAuth
rw-self-hostedus-docker.pkg.devGCP service-account JSON (RunWhen-issued) wired as custom upstream credentials
ghcrhttps://ghcr.ioAnonymous
dockerhubhttps://registry-1.docker.ioAnonymous (optional Hub creds avoid rate limits)
quayhttps://quay.ioAnonymous (optional)
k8shttps://registry.k8s.ioAnonymous (optional)

Direct-pull shortcut: Because RunWhen images already live in GAR (us-docker.pkg.dev), you can skip the rw-self-hosted remote repo and grant your cluster’s pull-secret SA Artifact Reader on the RunWhen-issued repository directly. The remote repo adds caching + your-project audit; direct pull is simpler but has no cache layer. Choose based on whether your network policy requires a single egress point.

Required settings per remote repo

SettingValueWhy it matters
Upstream registryPer table aboveThe public source this repo proxies
Allow tag listingEnabled on ghcrRequired so RunWhen’s code-collection catalog can list image tags via GET /v2/<image>/tags/list. Without it, the runner gets an empty catalog and tasks fail to dispatch.
Custom upstream credentials (on rw-self-hosted only)RunWhen GCP SA JSONGAR remote repos proxy an upstream using the repo’s own project SA by default. For the RunWhen GAR repo, you must supply the RunWhen-issued SA so the proxy can authenticate upstream.

Wire the RunWhen GCP service-account key

On rw-self-hosted only, configure Custom upstream credentials:

  • Username: _json_key
  • Password / key file: the contents of the GCP service-account JSON file RunWhen issued to your organization

Verify with a workstation docker pull before going further:

Terminal window
# Login to the GAR registry hostname only — not the project/repo path.
# (--password-stdin works with Docker and Podman; avoid --password-file.)
GAR_REGISTRY="<region>-docker.pkg.dev"
GAR_HOST="${GAR_REGISTRY}/<your-project>"
cat runwhen-sa-key.json | docker login "${GAR_REGISTRY}" --username _json_key --password-stdin
# Pull a RunWhen image through the remote repo
docker pull "${GAR_HOST}/rw-self-hosted/runwhen-self-hosted/platform-images/backend-services:<tag-from-onboarding>"

A 403 Forbidden or manifest unknown here almost always means an expired or wrongly-scoped RunWhen GCP key — contact RunWhen support before continuing. Do not apply a broken credential to your cluster.


4. Understand the path mapping (no virtual aggregator)

Unlike JFrog, GAR has no virtual repository layer. Each remote repo resolves to a distinct path prefix under your GAR hostname. After Step 3 the path translation is mechanical:

Upstream pathPath through GAR
us-docker.pkg.dev/runwhen-self-hosted/platform-images/backend-services:2026-05-20.1<gar-host>/rw-self-hosted/runwhen-self-hosted/platform-images/backend-services:2026-05-20.1
ghcr.io/runwhen-contrib/runwhen-platform-mcp:2026-05-12.1<gar-host>/ghcr/runwhen-contrib/runwhen-platform-mcp:2026-05-12.1
docker.io/hashicorp/vault:1.16.1<gar-host>/dockerhub/hashicorp/vault:1.16.1

The Helm chart artifact (charts/runwhen-platform) sits inside the same RunWhen GAR repo as the images, so it pulls through rw-self-hosted (or directly from us-docker.pkg.dev) — no second credential or repo.


5. (Optional) Handle Helm chart dependencies

GAR does not mirror classic HTTP Helm chart indexes (the helm repo add model). You have two options for the subchart dependencies (Bitnami, Neo4j, HashiCorp, Qdrant, SeaweedFS):

OptionWhen to useHow
Keep public indexesInstall workstation can reach public Helm repos during helm dependency updatehelm repo add bitnami https://charts.bitnami.com/bitnami etc. — no GAR involvement
OCI mirror into GARPolicy-restricted or air-gap installsCopy chart OCI artifacts into a GAR standard repo, then helm repo add the GAR OCI path

For most installs, keep the public indexes — the subchart deps are resolved once on the install workstation, not by the cluster at runtime.

Register the public Helm aliases on the install workstation. The alias names must match exactlyChart.yaml references them as @bitnami, @neo4j, @hashicorp, @qdrant, @seaweedfs:

Terminal window
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add neo4j https://helm.neo4j.com/neo4j
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo add qdrant https://qdrant.github.io/qdrant-helm
helm repo add seaweedfs https://seaweedfs.github.io/seaweedfs/helm
helm repo update

Pull the RunWhen chart via OCI through the rw-self-hosted remote repo (or directly from us-docker.pkg.dev). The helm registry login must run before helm pull so the credential is cached for the OCI fetch.

Terminal window
GAR_REGISTRY="<region>-docker.pkg.dev"
GAR_HOST="${GAR_REGISTRY}/<your-project>"
CHART_VERSION="0.2.58" # replace with the version from your onboarding pack
# Option A — short-lived access token (operator is `gcloud auth login`'d with
# a principal that has Artifact Reader on the repo). Token expires in ~1 h.
gcloud auth print-access-token | helm registry login "${GAR_REGISTRY}" \
--username oauth2accesstoken --password-stdin
# Option B — durable JSON key (reuse the RunWhen-issued SA or your reader SA).
# Better for CI or unattended pulls; no expiry within the key lifetime.
cat runwhen-reader-key.json | helm registry login "${GAR_REGISTRY}" \
--username _json_key --password-stdin
# Pull the chart (the login above makes this non-interactive)
helm pull \
oci://${GAR_HOST}/rw-self-hosted/runwhen-self-hosted/platform-images/charts/runwhen-platform \
--version "${CHART_VERSION}" --untar
# Resolve subchart dependencies on the install workstation
helm dependency update ./runwhen-platform

Direct-pull variant: If you skipped the rw-self-hosted remote repo (Step 3 shortcut) and pull the chart straight from RunWhen’s GAR, login to us-docker.pkg.dev with the RunWhen-issued SA instead, and drop the rw-self-hosted/ prefix from the oci:// path:

Terminal window
cat runwhen-sa-key.json | helm registry login us-docker.pkg.dev \
--username _json_key --password-stdin
helm pull \
oci://us-docker.pkg.dev/runwhen-self-hosted/platform-images/charts/runwhen-platform \
--version "${CHART_VERSION}" --untar

6. Create the cluster pull secret

Create a read-scoped GCP service account in your project (not the RunWhen-issued one — that’s for the upstream proxy only). Grant it Artifact Reader (roles/artifactregistry.reader) on all remote repos above, then generate a JSON key.

Terminal window
NS=runwhen-self-hosted
kubectl create namespace "$NS" --dry-run=client -o yaml | kubectl apply -f -
# Create the cluster SA (one-time)
GCP_PROJECT="<your-project>"
CLUSTER_SA="runwhen-registry-reader"
gcloud iam service-accounts create "$CLUSTER_SA" --project="$GCP_PROJECT"
# Grant Artifact Reader on each remote repo (repeat per repo)
for REPO in rw-self-hosted ghcr dockerhub; do
gcloud artifacts repositories add-iam-policy-binding "$REPO" \
--location="<region>" --project="$GCP_PROJECT" \
--member="serviceAccount:${CLUSTER_SA}@${GCP_PROJECT}.iam.gserviceaccount.com" \
--role="roles/artifactregistry.reader"
done
# Generate a JSON key
gcloud iam service-accounts keys create runwhen-reader-key.json \
--iam-account="${CLUSTER_SA}@${GCP_PROJECT}.iam.gserviceaccount.com" \
--project="$GCP_PROJECT"
# Create the Kubernetes pull secret
kubectl create secret docker-registry gcr-pull-secret \
--docker-server="<region>-docker.pkg.dev" \
--docker-username="_json_key" \
--docker-password="$(cat runwhen-reader-key.json)" \
--docker-email="ops@yourcompany.example" \
-n "$NS" \
--dry-run=client -o yaml | kubectl apply -f -

Use a read-scoped SA key (not your admin credential). The chart default expects the secret named gcr-pull-secret under images.pullSecrets[0].name. Override the name in your values if your naming policy requires something different.

Workload Identity alternative: If your cluster runs on GKE with Workload Identity enabled, bind the platform namespace’s Kubernetes service account to the GCP SA and skip the JSON key entirely — no docker-registry secret needed. This is the recommended production pattern on GKE.


7. Wire the GAR paths into Helm values

Use per-image registry: overrides (“Pattern 2” in the chart’s registry routing) since each upstream maps to a different repo prefix. RunWhen ships a worked example (values-example-airgap-gar.yaml) that uses YAML anchors to keep prefixes DRY:

x-gar-anchors:
gar-host: &gar_host "<region>-docker.pkg.dev/<your-project>"
gar-rwsh: &gar_rwsh "<region>-docker.pkg.dev/<your-project>/rw-self-hosted/runwhen-self-hosted/platform-images"
gar-ghcr: &gar_ghcr "<region>-docker.pkg.dev/<your-project>/ghcr/runwhen-contrib"
gar-dh: &gar_dh "<region>-docker.pkg.dev/<your-project>/dockerhub"
gar-ghcr-zalando: &gar_ghcr_zalando "<region>-docker.pkg.dev/<your-project>/ghcr/zalando"
global:
imagePullSecrets:
- name: gcr-pull-secret
images:
backendServices: { registry: *gar_rwsh }
agentFarm: { registry: *gar_rwsh }
runnerControl: { registry: *gar_rwsh }
webhooks: { registry: *gar_rwsh }
usearch: { registry: *gar_rwsh }
ui: { registry: *gar_rwsh }
sharedServices: { registry: *gar_rwsh }
mcpServer: { registry: *gar_ghcr }
ccCatalogSvc: { registry: *gar_ghcr }
cortexTenant: { registry: *gar_ghcr }

Rotate the GAR host? With YAML anchors, change the &gar_host string in one place — every image rewires automatically.

Run helm template -f values.yaml -f values-gar.yaml and confirm every rendered image string resolves in your GAR UI before install day.


8. Verification checklist

  • gcloud artifacts repositories list --location=<region> shows all remote repos
  • Each remote repo’s upstream registry is reachable and returns UP in the GAR console
  • helm registry login <region>-docker.pkg.dev succeeds (Option A or B) and the credential is cached before pulling
  • Test pulls succeed against each repo:
    • <gar-host>/dockerhub/library/busybox:1.36
    • <gar-host>/ghcr/berriai/litellm:<tag>
    • <gar-host>/rw-self-hosted/runwhen-self-hosted/platform-images/backend-services:<tag>
  • Allow tag listing enabled on ghcr — verify GET /v2/runwhen-contrib/<image>/tags/list returns tags through your repo
  • helm pull oci://<gar-host>/rw-self-hosted/.../charts/runwhen-platform --version 0.2.58 succeeds (use your onboarding-pack version)
  • helm dependency update ./runwhen-platform succeeds (Helm aliases resolve)
  • kubectl get secret gcr-pull-secret -n <namespace> shows the docker-config secret

9. Troubleshooting

SymptomMost likely cause
403 Forbidden pulling RunWhen images through rw-self-hostedRunWhen GCP SA expired, wrong upstream credentials, or SA not wired as custom creds on the remote repo
403 Forbidden pulling in-clusterCluster pull-secret SA missing roles/artifactregistry.reader on one or more repos
manifest unknown on first pull through a remoteGAR remote repo created but upstream not reachable — check egress / FQDN allowlist
Empty code-collection catalog at runtimeAllow tag listing disabled on ghcr remote repo
Helm chart pull fails with mime-type errorPulling the chart through the wrong repo — the OCI artifact must come via rw-self-hosted, not a dockerhub repo
helm dependency updateno such repositoryAlias name mismatch — must be bitnami, not bitnami-mirror etc.
ImagePullBackOff on subchart pods onlyMissing per-image registry: override — the value still points at docker.io/...
Random pulls fail at scaleDocker Hub anonymous rate limits — add Hub creds to the dockerhub remote repo’s upstream credentials
Pull-secret auth fails with deniedUsing the RunWhen-issued upstream SA as the cluster pull secret instead of a SA in your own project

Air-gap notes

For fully disconnected installs:

  • Do not rely on GAR’s pull-through cache. Pre-stage images on a connected host using crane or skopeo, then crane copy (or equivalent) into a GAR standard repo (not a remote repo) across the gap.
  • Mirror all five Helm chart repos as OCI artifacts into a GAR standard repo and complete helm dependency update on a host inside the gap.
  • The RunWhen Helm chart OCI artifact is included in the same runwhen-self-hosted repo as the images — copy it across with the same tooling.
  • See the self-hosted requirements page for the full list of cluster prerequisites that also need air-gap-friendly defaults (cert-manager mode, internal DNS, customer-supplied LLM, etc.).

Need the version-pinned image inventory, the GAR apply.sh script (gcloud Terraform equivalent), or the complete values-example-airgap-gar.yaml? Email support@runwhen.com — these ship in your onboarding pack.