Skip to main content
The platform test environment gives every engineer their own vCluster with the full Grounds stack — Velocity proxy, plugins, lobby, gamemodes, gRPC services — running against shared backends (NATS, Postgres, Keycloak) over the service-tailnet. You override only the components you’re actively working on, leave everything else on the bundle’s defaults, and iterate with DevSpace’s hot-reload loop.
This environment is for authorized Grounds engineers only. It relies on a private Tailscale network to reach shared backend services (NATS, Postgres, Keycloak); without an invite to the Grounds tailnet you can’t follow this guide end-to-end. If you’re an external contributor working on a public component, look at the public CI flow instead.
A bundle is a versioned, curated list of platform components and their default versions. You pin against a bundle release (for reproducible CI runs) or against the current edge (for daily iteration). The full schema is documented under Bundle Reference.

TL;DR

1. Prerequisites

Sanity check:
It validates auth, API reachability, and Tailscale routes for *.dev.

2. Bootstrap your vCluster

What this does:
1

Resolve project

Forge resolves your default project on the platform side.
2

Ensure host namespace

A per-engineer host namespace vcluster-<handle> (privileged PSA) is created if missing.
3

Install vCluster

helm install of loft/vcluster, idempotent — re-runs are no-ops if the vCluster already exists.
4

Wait for vCluster API

The reconciler polls until the inner API is up and pulls its kubeconfig out of a Secret.
5

Resolve bundle

The forge service fetches bundle.yaml @ 0.4.0 and merges it with your override file (if any) into a concrete component list.
6

Deploy components

Each component is helm install’d into the vCluster, best-effort — a chart-not-found on one component doesn’t abort the rest.
The output tells you which components landed and which ones errored:
Re-run grounds cluster up --bundle 0.4.0 after fixing the cause and the reconciler picks up where it left off.
Bundle pin vs. main--bundle 0.4.0 is reproducible: that exact composition gives you the same component versions every time. --bundle main always tracks the moving edge. Pin in CI, float locally.

3. Get your vCluster’s kubeconfig

For raw kubectl / helm / devspace access to the inner cluster, the kubeconfig lives in a Secret on the host namespace:
Each engineer’s vCluster runs separately on the platform cluster — your default namespace inside the vCluster is your stack.
A first-class grounds cluster kubeconfig subcommand is planned but not yet shipped — for now the manual fetch above is the supported path.

4. Override a component

Without overrides, every component runs the released image at the version pinned by the bundle. To swap one for your local code, write an override file:
overrides/me.yaml
Apply it:

Override schema

See Bundle Reference for the full override schema and field-level documentation.

5. Iterate on a component

The bundle’s per-component devspace.workflow field selects which template applies:
Use jar-sync-pod-restart:
DevSpace will:
  1. Run ./gradlew build locally.
  2. Sync the produced JAR into the plugin pod’s /shared/plugin.jar.
  3. kubectl rollout restart deployment/velocity — Velocity’s init-containers re-fetch your JAR.
  4. Watch build/libs/ and re-do all of the above on every change.

6. Inspect the event bus

The bundle has an opt-in nats-recorder component that subscribes to chosen subjects and emits each message as JSONL on stdout — useful for debugging cross-service events without writing one-off subscribers.
After grounds cluster up, tail it:
Subjects it listens on are configured in bundle.yaml (default: config.>, player.>, friends.>, match.>).

7. Tear down

down is cheap to reverse; delete requires re-running the bundle deploys from scratch. To swap one component out without losing state, use overrides — don’t delete.

8. Troubleshooting

The Helm chart for that component type isn’t published yet. The forge response’s failed[] block tells you exactly which component and which chart URL failed. Either wait for the chart to be released or override the failing component with enabled: false so the rest of the stack still comes up.
The grounds-velocity chart’s init-containers curl each plugin’s Service at http://<plugin>:8080/plugin.jar. If a plugin pod isn’t ready (e.g. its image doesn’t exist or it’s still pulling), the velocity pod waits. kubectl get pods and look for the plugin pods that aren’t Running.
The jar-sync-pod-restart workflow assumes you set RESTART_TARGET=velocity for plugin-* components. Without the rollout-restart, Velocity keeps the old JAR loaded. Check the onUpload.execRemote block in your devspace.yaml.
You’re not on the internal Grounds tailnet. This is a private network for authorized engineers — if you haven’t been invited yet, ask a maintainer. Once you’re on, run tailscale status to confirm the routes; if they’re missing, re-run tailscale up accepting the dev-routes ACL.
You already have a profile=platform workspace from the older flow. The bundle workflow uses profile=platform-bundle and they’re not interchangeable. grounds cluster delete first, then re-up with --bundle.
grounds cluster up --bundle X is idempotent and uses helm install/upgrade. If a chart’s values shape changed between bundle versions, a stale release can block the upgrade. Drop the specific component’s release inside the vCluster (helm uninstall <component> against the vCluster kubeconfig) and re-run cluster up.

9. Reference