Skip to main content
Use this when adding a new component to a bundle, debugging an unexpected resolved value, or building tooling that consumes the bundle. A bundle is a versioned, curated list of platform components and the default versions of each. Engineers pin against a bundle release for reproducible runs (CI), or against the moving edge for daily iteration. Forge resolves a bundle plus an optional override file into a concrete set of Helm releases, then applies them inside your per-engineer vCluster.

Top-level shape

shared

Backends that don’t run inside an engineer’s vCluster. Each in-vCluster service connects out via Tailscale to these endpoints.
The endpoints declared here are only reachable from the internal Grounds tailnet — a private Tailscale network for authorized engineers. External contributors won’t be able to resolve nats.dev / postgres.dev / keycloak.dev. The bundle composition is still publicly readable; only the runtime is gated.
The shared block is informational — forge doesn’t push these values into helm values automatically. The component charts are responsible for connecting using the same names (e.g. the in-vCluster service-config chart has NATS_URL=nats://nats.dev:4222 baked into its env).

components

A map: each key is the Helm release name (also used as namespace-DNS-label inside the vCluster, so it must be a valid DNS-1123 label — lowercase, hyphens, no underscores). Each value is a BundleComponent:

BundleComponent fields

Component types

The type field is a string for forward-compat, but the platform-test environment understands these values: Forge uses type only for default-behavior decisions (today: which DevSpace workflow template to suggest). Adding a new type is a bundle PR, not a forge PR.

Helm value merging

The values forge passes to helm install for a component are produced by:
1

Start with the component's helm: block

Or {} if absent.
2

Inject image.repository + image.tag

Deep-merge { image: { repository: <component.image>, tag: <resolved-tag> } } on top.
3

Inject DevSpace pickup annotations (when gradle-local)

Deep-merge a podAnnotations block:
Deep-merge rules:
  • Plain-object values are merged recursively.
  • Arrays are replaced wholesale (no list-merge).
  • Primitives are replaced.
helm.image.tag = "0.5.0" in the bundle is not a useful value — forge will overwrite it with the resolved tag. Pin via the version: field instead.

Override-File schema

Engineers write an override YAML and pass it to grounds cluster up --bundle X --override ./me.yaml. Schema:
<ComponentOverride> is a discriminated union — exactly one of these shapes:
Use the bundle’s image but optionally override the tag.

Pod annotations

The fields project, artifact, reload, and devspace aren’t applied by forge — they’re echoed as Pod annotations for DevSpace to read on the engineer’s laptop. The annotations forge writes:

Resolution algorithm

When forge gets (bundle, override-file):
1

Filter components

Drop everything where override.enabled === false. Drop optional: true components that aren’t explicitly enabled.
2

Resolve per remaining component

  • If override is mode: image with tag: replace imageTag with the override.
  • If override is mode: gradle-local: keep image+tag from bundle; mark mode for annotation injection later.
  • If no override: use bundle defaults.
3

Compute helm values

Deep-merge per the rules above.
4

Helm install

Best-effort, parallel-friendly. If one component’s chart is missing, the others still go through. Failed components are reported in the response body but don’t fail the call.

Adding a new component

1

Ensure the chart exists

Make sure the component-type’s Helm chart is in groundsgg/charts and published as an OCI artifact (or co-publish it in the same PR).
2

Add a components: entry to bundle.yaml

3

Mark optional if it shouldn't run by default

Add optional: true and document the opt-in in examples/.
4

Open the PR

release-please bumps the bundle version on merge.

Versioning

release-please drives the bundle version: Engineers pin against bundle versions:
bundle: main always tracks the latest commit on main — useful for daily iteration, not for CI.

See also