Skip to content

JFrog Artifactory / JCR Setup for RunWhen Self-Hosted

This guide is for platform engineering and registry teams standing up the enterprise registry that will host RunWhen images before the Self-Hosted RunWhen Platform install. It covers JFrog Artifactory (Pro / Enterprise / Cloud) and the free JCR edition.

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.
  • JFrog admin rights on the host that will own these repositories.
  • Outbound HTTPS (443) from JFrog 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.)

How RunWhen flows through JFrog

JFrog data flow: upstream registries proxy through JFrog Docker remotes, aggregate behind a Docker virtual repo, and serve images to the RunWhen Platform Kubernetes cluster via a single pull secret.

The shape is the same for every enterprise install:

  1. One Docker remote per upstream registry in JFrog. RunWhen images get a dedicated, authenticated remote; everything else uses anonymous remotes.
  2. One Docker virtual repository aggregates the remotes behind a single host path so cluster pulls don’t have to know about individual upstreams.
  3. One Kubernetes pull secret authenticates against your JFrog host. The chart names it gcr-pull-secret by default — feel free to keep that name even though no GCR is involved at this point.
  4. Helm values route each image to the correct path under the virtual repo. RunWhen ships a worked overlay for JFrog (values-example-airgap-jcr.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 docker-runwhen-self-hosted remote — no second credential.


1. Pick the JFrog edition

EditionHelm remote reposREST bulk configRecommended for
Artifactory Cloud / Enterprise+YesYesMulti-site, federated, project-scoped enterprise installs
Artifactory ProYesYesStandard enterprise self-hosted installs (most customers)
JCR FreeYesLimitedSmall to mid-size single-site installs

Recommendation: Artifactory Pro or Enterprise for production. JCR Free is a viable starting point for single-site or proof-of-concept installs — both Docker and Helm remotes work — but you give up federation, projects, and most of the bulk-management REST surface.


2. Whitelist upstream FQDNs from JFrog

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

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 JFrog)
registry.k8s.ioOptionalingress-nginx, external-dns (if proxied)

3. Create the Docker remote repositories

Create one remote per upstream. Remote keys below are the names that drive pull paths under the virtual aggregator — keep them exactly as shown unless you have a strong reason to deviate (RunWhen’s example values overlay assumes these names).

Remote keyUpstream URLAuth
docker-runwhen-self-hostedhttps://us-docker.pkg.devGCP service-account JSON (RunWhen-issued)
docker-ghcrhttps://ghcr.ioAnonymous
docker-dockerhubhttps://registry-1.docker.ioAnonymous (optional Hub creds avoid rate limits)
docker-quayhttps://quay.ioAnonymous (optional)
docker-k8shttps://registry.k8s.ioAnonymous (optional)

Required settings per remote

SettingValueWhy it matters
Includes patternSee table belowScopes the cache to RunWhen-needed paths and prevents accidental proxying of unrelated content
List remote folder itemsEnabled on docker-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.
Block mismatching mime typesDisabledOCI Helm artifacts use a non-Docker mime type. Leaving this enabled blocks chart pulls.

Includes patterns

RemoteIncludes pattern
docker-runwhen-self-hostedrunwhen-self-hosted/platform-images/**
docker-ghcrrunwhen-contrib/**, berriai/**, zalando/**
docker-dockerhubNarrow to known third-party paths from your onboarding inventory (Vault, Redis, Mimir, Qdrant, Neo4j, SeaweedFS) — avoid * in production

Wire the RunWhen GCP service-account key

On docker-runwhen-self-hosted only:

  • Username: _json_key
  • Password: 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
docker login <jcr-host> --username <jfrog-user> --password <jfrog-token>
docker pull <jcr-host>/docker/runwhen-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 GCP key — contact RunWhen support before continuing. Do not apply a broken credential to your cluster.


4. Create the Docker virtual aggregator

Create one virtual Docker repository so cluster pods see a single host path.

  • Repository key: docker
  • Members (ordered): docker-runwhen-self-hosted, docker-ghcr, docker-dockerhub, plus optional remotes
  • Advanced setting: “Artifactory Requests Can Retrieve Remote Artifacts”enabled. Without this flag the virtual returns only locally-cached artifacts and every first-time pull 404s with no upstream attempt.

Path-mapping reference

After Step 4 the path translation is mechanical:

Upstream pathPath through JFrog
us-docker.pkg.dev/runwhen-self-hosted/platform-images/backend-services:2026-05-20.1<jcr-host>/docker/runwhen-self-hosted/runwhen-self-hosted/platform-images/backend-services:2026-05-20.1
ghcr.io/runwhen-contrib/runwhen-platform-mcp:2026-05-12.1<jcr-host>/docker/ghcr/runwhen-contrib/runwhen-platform-mcp:2026-05-12.1
docker.io/hashicorp/vault:1.16.1<jcr-host>/docker/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 docker-runwhen-self-hosted — no second credential or repo.


5. (Optional) Create Helm remote repositories

Skip this step if your install workstation can reach public Helm chart indexes during helm dependency update. For policy-restricted or air-gap installs, mirror them through JFrog. Helm remotes are supported on every JFrog edition, including JCR Free.

Remote keyUpstream URLRequired when
helm-bitnamihttps://charts.bitnami.com/bitnamiAlways (redis subchart; postgres if postgresql.kind=bundled)
helm-neo4jhttps://helm.neo4j.com/neo4jneo4j.deploy=true (default)
helm-hashicorphttps://helm.releases.hashicorp.comvault.deploy=true (default)
helm-qdranthttps://qdrant.github.io/qdrant-helmqdrant.useSubchart=true
helm-seaweedfshttps://seaweedfs.github.io/seaweedfs/helmseaweedfs.deploy=true (default)

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

Terminal window
export JCR_HOST="artifactory.example.com"
export JCR_USER="<jfrog-user>"
export JCR_PASS="<jfrog-token>"
helm repo add bitnami "https://${JCR_HOST}/artifactory/helm-bitnami" --username "$JCR_USER" --password "$JCR_PASS"
helm repo add neo4j "https://${JCR_HOST}/artifactory/helm-neo4j" --username "$JCR_USER" --password "$JCR_PASS"
helm repo add hashicorp "https://${JCR_HOST}/artifactory/helm-hashicorp" --username "$JCR_USER" --password "$JCR_PASS"
helm repo add qdrant "https://${JCR_HOST}/artifactory/helm-qdrant" --username "$JCR_USER" --password "$JCR_PASS"
helm repo add seaweedfs "https://${JCR_HOST}/artifactory/helm-seaweedfs" --username "$JCR_USER" --password "$JCR_PASS"
helm repo update

Pull the RunWhen chart via OCI through the Docker virtual (helm registry login is not used for classic Helm repo indexes):

Terminal window
helm registry login "${JCR_HOST}" --username "$JCR_USER" --password "$JCR_PASS"
helm pull \
oci://${JCR_HOST}/docker/runwhen-self-hosted/runwhen-self-hosted/platform-images/charts/runwhen-platform \
--version <CHART_VERSION> --untar
helm dependency update ./runwhen-platform

6. Create the cluster pull secret

Terminal window
NS=runwhen-self-hosted
kubectl create namespace "$NS" --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret docker-registry gcr-pull-secret \
--docker-server="<jcr-host>" \
--docker-username="<jfrog-reader-user>" \
--docker-password="<jfrog-identity-or-access-token>" \
--docker-email="ops@yourcompany.example" \
-n "$NS" \
--dry-run=client -o yaml | kubectl apply -f -

Use a read-scoped JFrog identity / access token (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.


7. Wire the JFrog paths into Helm values

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

x-jcr-anchors:
jcr-host: &jcr_host "artifactory.example.com"
jcr-rwsh: &jcr_rwsh "artifactory.example.com/docker/runwhen-self-hosted/runwhen-self-hosted/platform-images"
jcr-ghcr: &jcr_ghcr "artifactory.example.com/docker/ghcr/runwhen-contrib"
jcr-dh: &jcr_dh "artifactory.example.com/docker/dockerhub"
jcr-ghcr-zalando: &jcr_ghcr_zalando "artifactory.example.com/docker/ghcr/zalando"
global:
imagePullSecrets:
- name: gcr-pull-secret
images:
backendServices: { registry: *jcr_rwsh }
agentFarm: { registry: *jcr_rwsh }
runnerControl: { registry: *jcr_rwsh }
webhooks: { registry: *jcr_rwsh }
usearch: { registry: *jcr_rwsh }
ui: { registry: *jcr_rwsh }
sharedServices: { registry: *jcr_rwsh }
mcpServer: { registry: *jcr_ghcr }
ccCatalogSvc: { registry: *jcr_ghcr }
cortexTenant: { registry: *jcr_ghcr }

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

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


8. Verification checklist

  • curl -sf https://<jcr-host>/artifactory/api/system/ping returns OK
  • Each Docker remote returns HTTP 200 from /api/repositories/<key> (Pro / Enterprise)
  • Test pulls succeed against the virtual:
    • <jcr-host>/docker/dockerhub/library/busybox:1.36
    • <jcr-host>/docker/ghcr/berriai/litellm:<tag>
    • <jcr-host>/docker/runwhen-self-hosted/runwhen-self-hosted/platform-images/backend-services:<tag>
  • List Remote Folder Items enabled on docker-ghcr — verify GET /v2/runwhen-contrib/<image>/tags/list returns tags through your virtual
  • helm pull oci://...charts/runwhen-platform succeeds
  • 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 imagesRunWhen GCP SA expired, wrong includesPattern, or SA not wired on docker-runwhen-self-hosted
manifest unknown on first pull through the virtualVirtual repo missing “Artifactory Requests Can Retrieve Remote Artifacts”
Empty code-collection catalog at runtimeList Remote Folder Items disabled on docker-ghcr
Helm chart pull fails with mime-type errorBlock Mismatching Mime Types is enabled on docker-runwhen-self-hosted — disable it
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 docker-dockerhub

Air-gap notes

For fully disconnected installs:

  • Do not rely on JFrog’s pull-through cache. Pre-stage images on a connected host using crane or skopeo, then crane copy (or equivalent) into JFrog across the gap.
  • Mirror all five Helm chart repos as JFrog Helm remotes (Step 5) 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 JFrog REST apply.sh script (Pro / Enterprise), or the complete values-example-airgap-jcr.yaml? Email support@runwhen.com — these ship in your onboarding pack.