Skip to content

Commit a9e5057

Browse files
aa-replicatedclaude
andcommitted
docs: add EC3 helm image routing guide
Complete guide for configuring a Helm chart to work with EC3 v3 across all three install paths (CMX, EC3 online, EC3 airgap) in a single release. Covers helmchart.yaml values/builder sections, ReplicatedImageRegistry usage, SDK special case, values.yaml defaults, and debugging table. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 638594c commit a9e5057

File tree

1 file changed

+284
-0
lines changed

1 file changed

+284
-0
lines changed

docs/ec3-helm-image-routing.md

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
# Getting Your Helm Chart to Work with Embedded Cluster v3: Online and Airgap
2+
3+
This guide covers everything you need to configure a Helm chart to work correctly across all three install paths — Helm CLI (CMX), EC3 online, and EC3 airgap — from a single release.
4+
5+
The official docs cover each piece in separate places. This doc assembles the complete picture in one place.
6+
7+
---
8+
9+
## The Three Install Paths
10+
11+
| Path | How it works | Who configures image routing |
12+
|------|-------------|------------------------------|
13+
| **Helm CLI (CMX)** | Customer runs `helm install` directly | Your `values.yaml` defaults |
14+
| **EC3 online** | EC3 installs your chart; nodes have internet access | `helmchart.yaml` `values` section at install time |
15+
| **EC3 airgap** | EC3 installs from a bundle with no internet access | `helmchart.yaml` `values` section + `builder` section at bundle-build time |
16+
17+
`helmchart.yaml` is a KOTS custom resource — it is invisible to `helm install`. It only applies when KOTS/EC3 is the installer.
18+
19+
---
20+
21+
## How EC3 Routes Images
22+
23+
EC3 v3 uses two mechanisms depending on the install type:
24+
25+
**Online:** EC3 configures containerd with registry mirrors pointing to the Replicated proxy (`proxy.replicated.com/proxy/<app-slug>/<original-registry>`). Your chart's image references are rewritten at the `helmchart.yaml` level before Helm sees them.
26+
27+
**Airgap:** EC3 bundles all images into a `.airgap` file at release-build time. At install time it loads them into a local embedded registry (e.g. `10.244.128.11:5000`). Your chart's image references must point to this local registry. EC3 configures containerd mirrors for each registry you declared via `ReplicatedImageRegistry` in `helmchart.yaml`.
28+
29+
---
30+
31+
## The Template Functions
32+
33+
Only a small set of Replicated template functions are available in `helmchart.yaml`. The ones you need for image routing:
34+
35+
### `ReplicatedImageRegistry "registry-host"`
36+
37+
Takes an original registry hostname. Returns:
38+
- **Online:** `proxy.replicated.com/proxy/<app-slug>/<registry-host>`
39+
- **Airgap:** The local embedded registry address (e.g. `10.244.128.11:5000`)
40+
41+
Use this for any image where the registry and repository are separate fields in the chart values (the common case for Bitnami charts and most others).
42+
43+
```yaml
44+
# Online result: proxy.replicated.com/proxy/myapp/ghcr.io
45+
# Airgap result: 10.244.128.11:5000
46+
registry: 'repl{{ ReplicatedImageRegistry "ghcr.io" }}'
47+
```
48+
49+
**Important:** Pass only the registry hostname — not the full image ref. The repository stays as-is in your chart's values.
50+
51+
### `ReplicatedImageRegistry "registry-host" true`
52+
53+
The `true` flag is `noProxy`. Behavior:
54+
- **Online:** Returns the registry hostname unchanged (no proxy wrapping)
55+
- **Airgap:** Still returns the local embedded registry address
56+
57+
Use this for images that are already hosted at a proxy-aware address and should not be double-wrapped online, but still need local registry routing for airgap.
58+
59+
```yaml
60+
# Online result: proxy.replicated.com (unchanged)
61+
# Airgap result: 10.244.128.11:5000
62+
registry: 'repl{{ ReplicatedImageRegistry "proxy.replicated.com" true }}'
63+
```
64+
65+
### `ReplicatedImageName "full-image-ref"`
66+
67+
Returns the complete rewritten image reference (registry + repository + tag). Use this only when your chart has a single combined `image:` field rather than separate `registry:` and `repository:` fields.
68+
69+
---
70+
71+
## The `values` Section
72+
73+
The `values` section in `helmchart.yaml` is merged on top of your `values.yaml` at install time, before Helm renders templates. Template functions are evaluated in the customer's environment.
74+
75+
### Your main app image
76+
77+
Your Helm template constructs the image as `{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}`. Override only `registry` — keep `repository` as the bare path (no registry prefix):
78+
79+
```yaml
80+
values:
81+
image:
82+
registry: 'repl{{ ReplicatedImageRegistry "ghcr.io" }}'
83+
repository: 'myorg/myapp' # bare path, no registry prefix
84+
pullPolicy: IfNotPresent
85+
```
86+
87+
Do NOT set `repository` to the full proxy URL here. `ReplicatedImageRegistry` returns the registry prefix; Helm concatenates the repository to it.
88+
89+
### Bitnami subcharts (postgresql, redis, etc.)
90+
91+
Bitnami charts split image into `registry` and `repository`. The chart's default `repository` (`bitnami/postgresql`, `bitnami/redis`) is correct — only override `registry`:
92+
93+
```yaml
94+
postgresql:
95+
image:
96+
registry: 'repl{{ ReplicatedImageRegistry "index.docker.io" }}'
97+
redis:
98+
image:
99+
registry: 'repl{{ ReplicatedImageRegistry "index.docker.io" }}'
100+
```
101+
102+
Use `index.docker.io`, not `docker.io`. The Replicated proxy distinguishes between them.
103+
104+
### The Replicated SDK subchart
105+
106+
The SDK image lives at `proxy.replicated.com/library/replicated-sdk-image`. It is a special case:
107+
108+
- For **online**, the SDK works without any override — `proxy.replicated.com` is reachable directly.
109+
- For **airgap**, the SDK needs to be redirected to the local registry. Use `noProxy=true` so online installs are unaffected:
110+
111+
```yaml
112+
replicated:
113+
image:
114+
registry: 'repl{{ ReplicatedImageRegistry "proxy.replicated.com" true }}'
115+
```
116+
117+
> **What does NOT work:**
118+
> - `ReplicatedImageRegistry "proxy.replicated.com"` (no noProxy) → online produces a doubled proxy URL (`proxy.replicated.com/proxy/myapp/proxy.replicated.com/...`)
119+
> - `HasLocalRegistry` and `LocalRegistryHost` are **not available** in `helmchart.yaml` — they exist in other KOTS template contexts but not here
120+
> - `ReplicatedImageRegistry (HelmValue ".replicated.image.registry")` — `HelmValue` passes `proxy.replicated.com` as a runtime value but the behavior is unreliable in EC3 beta.1
121+
122+
### `imagePullSecrets` for Bitnami subcharts
123+
124+
Bitnami charts check `global.imagePullSecrets`. Set this in your `values.yaml` so it applies to all subcharts automatically:
125+
126+
```yaml
127+
global:
128+
imagePullSecrets:
129+
- name: enterprise-pull-secret
130+
security:
131+
allowInsecureImages: true # required for the local embedded registry (no TLS)
132+
```
133+
134+
`allowInsecureImages: true` is required because the EC3 embedded registry is HTTP, not HTTPS.
135+
136+
---
137+
138+
## The `builder` Section
139+
140+
The `builder` section is used **only by the Replicated Vendor Portal at release-build time** — never at runtime on the customer's machine. Its purpose: render your chart templates with static values so the portal can discover every container image and pull them into the `.airgap` bundle.
141+
142+
Rules:
143+
- No template functions — only static values
144+
- Must include every image that needs to be in the airgap bundle
145+
- Use the **true source registry** for each image (not the proxy URL)
146+
147+
```yaml
148+
builder:
149+
image:
150+
registry: "ghcr.io"
151+
repository: "myorg/myapp" # must match your chart's default repository
152+
replicated:
153+
image:
154+
registry: "proxy.replicated.com"
155+
repository: "library/replicated-sdk-image"
156+
postgresql:
157+
image:
158+
registry: "docker.io" # Bitnami chart defaults to docker.io
159+
redis:
160+
image:
161+
registry: "docker.io"
162+
```
163+
164+
**If your main app image is missing from `builder`:** The portal falls back to `values.yaml` defaults. If those defaults are set to the full proxy URL (e.g. `proxy.replicated.com/proxy/myapp/ghcr.io/myorg/myapp`) rather than the source registry, the portal may not correctly resolve and bundle the image. The airgap install will then show `not found` against the local registry even though the routing is correct.
165+
166+
**If an image is in `values` but not `builder`:** That image will not be in the airgap bundle. The pod will fail with either `not found` (local registry) or `i/o timeout` (trying to reach the internet).
167+
168+
---
169+
170+
## The `values.yaml` Defaults
171+
172+
`values.yaml` defaults apply to Helm CLI (CMX) installs. Set them to the full proxy URL format so CMX online installs work without any `--set` flags:
173+
174+
```yaml
175+
image:
176+
registry: proxy.replicated.com
177+
repository: proxy/myapp/ghcr.io/myorg/myapp
178+
tag: ""
179+
pullPolicy: IfNotPresent
180+
181+
postgresql:
182+
image:
183+
registry: proxy.replicated.com/proxy/myapp/index.docker.io
184+
185+
redis:
186+
image:
187+
registry: proxy.replicated.com/proxy/myapp/index.docker.io
188+
```
189+
190+
The format is `proxy.replicated.com/proxy/<app-slug>/<upstream-registry>`.
191+
192+
---
193+
194+
## The Deployment Template
195+
196+
Your deployment template should construct the image ref as:
197+
198+
```yaml
199+
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
200+
```
201+
202+
Do not add conditionals that check `imageProxy.host` or try to build the proxy path in the template. EC3 overrides `image.registry` via `helmchart.yaml` before the template renders — let it do the work.
203+
204+
---
205+
206+
## Complete Working `helmchart.yaml`
207+
208+
```yaml
209+
apiVersion: kots.io/v1beta2
210+
kind: HelmChart
211+
metadata:
212+
name: myapp
213+
spec:
214+
chart:
215+
name: myapp
216+
chartVersion: "0.0.0" # replaced by CI at release time
217+
values:
218+
# --- KOTS config/license values ---
219+
adminSecret: repl{{ ConfigOption `admin_secret`}}
220+
siteName: repl{{ ConfigOption `site_name`}}
221+
customBrandingEnabled: repl{{ LicenseFieldValue `custom_branding_enabled` }}
222+
223+
# --- Main app image ---
224+
image:
225+
registry: 'repl{{ ReplicatedImageRegistry "ghcr.io" }}'
226+
repository: 'myorg/myapp'
227+
pullPolicy: IfNotPresent
228+
229+
# --- Replicated SDK subchart ---
230+
replicated:
231+
image:
232+
registry: 'repl{{ ReplicatedImageRegistry "proxy.replicated.com" true }}'
233+
234+
# --- Bitnami postgresql subchart ---
235+
postgresql:
236+
image:
237+
registry: 'repl{{ ReplicatedImageRegistry "index.docker.io" }}'
238+
239+
# --- Bitnami redis subchart ---
240+
redis:
241+
image:
242+
registry: 'repl{{ ReplicatedImageRegistry "index.docker.io" }}'
243+
244+
builder:
245+
image:
246+
registry: "ghcr.io"
247+
repository: "myorg/myapp"
248+
replicated:
249+
image:
250+
registry: "proxy.replicated.com"
251+
repository: "library/replicated-sdk-image"
252+
postgresql:
253+
image:
254+
registry: "docker.io"
255+
redis:
256+
image:
257+
registry: "docker.io"
258+
```
259+
260+
---
261+
262+
## Checklist
263+
264+
Before testing an airgap install:
265+
266+
- [ ] Every image has a `ReplicatedImageRegistry` override in `values` (or is handled automatically by EC3)
267+
- [ ] Every image has a corresponding entry in `builder` with the true source registry
268+
- [ ] `values.yaml` defaults use the full proxy URL format for CMX installs
269+
- [ ] `global.imagePullSecrets` is set in `values.yaml` for subchart image pull auth
270+
- [ ] `global.security.allowInsecureImages: true` is set for the embedded registry
271+
- [ ] The deployment template does not build the proxy URL itself — it uses `image.registry/image.repository:tag` directly
272+
- [ ] A new release has been promoted in the Vendor Portal **after** `builder` changes, so the airgap bundle is rebuilt
273+
274+
---
275+
276+
## Debugging Image Pull Failures
277+
278+
| Error | Likely cause |
279+
|-------|-------------|
280+
| `proxy.replicated.com/proxy/myapp/proxy.replicated.com/...` | `ReplicatedImageRegistry` called without `noProxy=true` on an image already at `proxy.replicated.com` |
281+
| `docker.io/library/replicated-sdk-image` | `ReplicatedImageRegistry` returned empty string; the registry override resolved to nothing |
282+
| `10.x.x.x:5000/myimage: not found` | Routing is correct but image was not bundled — check `builder` section and rebuild airgap bundle |
283+
| `proxy.replicated.com/...: i/o timeout` | Airgap node trying to reach internet — image has no `ReplicatedImageRegistry` override or was not bundled |
284+
| `function "HasLocalRegistry" not defined` | `HasLocalRegistry`/`LocalRegistryHost` are not available in `helmchart.yaml` — use `ReplicatedImageRegistry` instead |

0 commit comments

Comments
 (0)