Install with Helm
Prepare a Kubernetes cluster, choose secure chart values, deploy Gameplane, bootstrap access, and verify the control plane before hosting servers.
Gameplane runs on Kubernetes and deploys via Helm. This guide walks through preparing a cluster, configuring the Helm chart, installing Gameplane, and verifying a working control plane.
Releases up to and including v0.3.0 are published under ghcr.io/valgulnecron, so this page uses that registry; releases after v0.3.0 move to ghcr.io/gameplanepanel.
Prerequisites and topology
Start with a supported Kubernetes and Helm version, ready nodes, DNS, and a default or explicitly selected StorageClass.
Save the exact chart version and rendered values used for every environment. They are part of your recovery plan—needed for disaster recovery, upgrades, and audits.
Network and DNS
Plan namespaces, ingress topology, and DNS before install:
- Gameplane namespace: the control plane runs in
gameplane-system(configurable via Helm) - Games namespace: game servers run in
gameplane-games(configurable viagamesNamespace) - Ingress hostname: reserve a DNS name for the dashboard (e.g.,
gameplane.your-domain.test) - External API address (optional): if you plan cluster operations (joining nodes, downloading kubeconfig) from outside the cluster, provide the external Kubernetes API address (e.g.,
1.2.3.4:6443orhttps://k8s.example.com:6443)
Node and pod capacity
Reserve capacity for the control plane:
- Operator: 100m CPU / 64 MiB memory request; 500m / 256 MiB limit
- API: 100m CPU / 64 MiB memory request; 500m / 256 MiB limit
- Web dashboard: typically 10–20m CPU / 32 MiB memory when idle
Game server pods request/limit independently via GameTemplate configuration.
Configure chart values
Before installing, decide on external URL, ingress, TLS, storage, authentication, cluster operations, telemetry, and update policy. These cannot be easily changed post-install without re-rendering values.
Essential values
# Helm chart values (e.g., in values.override.yaml)
ingress:
host: gameplane.your-domain.test # Dashboard hostname
tls: true # Provide a cert via cert-manager or a pre-created gameplane-tls Secret
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod # cert-manager ClusterIssuer for TLS
operator:
gameDataStorage:
storageClassName: "standard" # StorageClass for game server PVCs (empty = cluster default)
api:
db:
driver: sqlite # sqlite (default) | postgres (experimental)
# SQLite is production-tested and recommended for clusters up to ~100 servers
Optional: OIDC authentication at install time
If you use OIDC (OpenID Connect) for single sign-on, the chart can configure the provider at install time. In v0.2.0-beta.8 the chart accepts only the connection settings; map IdP groups to roles per provider under Admin Settings → Authentication after install:
# v0.2.0-beta.8
api:
oidc:
enabled: true
issuer: "https://your-idp.example.com"
clientID: "gameplane-app"
clientSecretRef:
name: "oidc-client-secret" # k8s Secret with 'clientSecret' key
redirectURL: "https://gameplane.your-domain.test/auth/oidc/callback"
From v0.3.0 the chart can also seed the provider’s display name and role mappings. Add these keys only when installing a v0.3.0 or later chart:
# v0.3.0 and later (in addition to the keys above)
api:
oidc:
displayName: "My SSO Provider"
groupsClaim: "groups" # which OIDC claim to read (empty = "groups"); does not turn mapping on or off
defaultRole: "viewer" # Default role when group mapping doesn't match
roleMappings:
admin:
- "gameplane-admins" # OIDC group(s) that get admin role
operator:
- "gameplane-operators"
viewer:
- "gameplane-viewers"
No bootstrap-admin run is needed if OIDC is configured; new users inherit their mapped role on first login.
Network policies and egress control
Enable default-deny policies in the games namespace to prevent pod-to-pod and outbound access by default:
networkPolicies:
enabled: true
# Allow kubelet liveness/readiness probes (a list; empty falls back to
# RFC1918 + link-local)
kubeletCIDRs:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- 169.254.0.0/16
# Allow player ingress (TCP/UDP for game traffic)
gameIngress:
enabled: true
fromCIDRs: ["0.0.0.0/0"] # Players from anywhere (restrict to your network)
# Allow public-internet egress (downloads, mod registries)
gameEgress:
enabled: true
ports:
- { protocol: TCP, port: 443 }
- { protocol: TCP, port: 80 }
Module sources and catalog
By default, Gameplane ships with the official game catalog (16 modules in v0.2.0, 30 in v0.3.0):
defaultModuleSource:
enabled: true
type: oci # Pull pre-built modules from OCI registry
oci:
url: ghcr.io/valgulnecron/gameplane-modules
verify:
enabled: true # Verify cosign signatures on official modules
To manage sources via GitOps instead of letting Helm create the default, set enabled: false and apply ModuleSource resources manually.
Full reference
See Helm Values Reference for a complete walkthrough of all Helm values, defaults, and advanced options.
Install and verify
Deploy the pinned OCI chart, bootstrap the first administrator, then validate pods, CRDs, dashboard access, and a test reconciliation.
Step 1: Install the Helm chart
Install a tagged release straight from the GitHub Container Registry (GHCR). Replace <version> with a release, e.g., 0.2.0-beta.8:
helm upgrade --install gameplane oci://ghcr.io/valgulnecron/charts/gameplane \
--version 0.2.0-beta.8 \
--namespace gameplane-system --create-namespace \
--values values.override.yaml
The chart is OCI-packaged — no helm repo add is needed. The appVersion field pins matching component images, so no image overrides are required for a released version.
Edge channel (latest development): To track rolling :edge images from the main branch instead of a released version:
helm upgrade --install gameplane oci://ghcr.io/valgulnecron/charts/gameplane \
--version <version> --set image.tag=edge \
--namespace gameplane-system --create-namespace
Step 2: Bootstrap the first administrator
Create an initial admin account. Passwords must be at least 12 characters.
kubectl -n gameplane-system exec deploy/gameplane-api -- \
/api bootstrap-admin --username admin --password "<choose>"
To avoid the password landing in your shell history, pipe it on stdin:
printf '%s' "$ADMIN_PASSWORD" | kubectl -n gameplane-system exec -i deploy/gameplane-api -- \
/api bootstrap-admin --username admin --password-stdin
If a user already exists, pass --force to rotate the password, promote them to admin, and end their existing sessions.
Step 3: Verify the installation
Check that all pods are running and CRDs are installed:
kubectl -n gameplane-system get pods
kubectl get crds | grep gameplane
Expected output:
backups.gameplane.local
backupschedules.gameplane.local
clusters.gameplane.local
gameservers.gameplane.local
gametemplates.gameplane.local
modules.gameplane.local
modulesources.gameplane.local
restores.gameplane.local
NetworkCapture (packet capture for protocol debugging) will be the 9th CRD, arriving in v0.3.0-rc.1.
Step 4: Access the dashboard
Open https://<ingress.host> (or the hostname you configured in ingress.host) and log in with your admin credentials.
Step 5: Test reconciliation with a sample server
Create a simple test GameServer to verify the operator is reconciling correctly:
- From the dashboard, Servers → Create a server
- Choose a module (e.g., Minecraft)
- Name it
test-serverand click Create - Watch the pod spin up in
kubectl get pods -n gameplane-games; the server should reach theReadystate within 1–2 minutes
INSTALL CHECK
Verifying image signatures
Every published image (tagged releases and :edge), the Helm chart, and official module bundles are signed with the project’s cosign key, cosign.pub, and recorded in the public Sigstore Rekor transparency log:
cosign verify --key cosign.pub \
ghcr.io/valgulnecron/gameplane/operator:0.2.0-beta.8
Pre-rotation releases (v0.2.0-beta.7 and earlier) used the retired Ed25519 key and lack transparency log entries — verify those with cosign-legacy.pub and --insecure-ignore-tlog=true.
Troubleshooting
Pod fails to start (“ImagePullBackOff”, “ErrImageNeverPull”)
The Helm chart defaults to pulling images from GitHub Container Registry (ghcr.io). For air-gapped clusters where Docker Hub or GHCR is unreachable, retag the images to a private registry mirror and set image.registry:
helm upgrade --install gameplane oci://ghcr.io/valgulnecron/charts/gameplane \
--version 0.2.0-beta.8 \
--set image.registry=your-private-registry.com
GHCR packages are private on first publish. The maintainer makes the core packages (gameplane/operator, gameplane/api, gameplane/agent, gameplane/web, and charts/gameplane) public so anonymous pulls work. For a private install, create a kubernetes.io/dockerconfigjson pull secret and set image.pullSecrets.
API pod crashes or keeps restarting
Check the API logs:
kubectl -n gameplane-system logs deploy/gameplane-api
Common issues:
- SQLite database locked: SQLite is single-writer on a ReadWriteOnce volume, so running multiple API replicas is not supported. Keep
api.replicas: 1. PostgreSQL is experimental and does not make extra replicas safe either: the user-management lock and audit hash chain remain per-process. - Missing default StorageClass: The API needs a PVC for the SQLite database. Verify with
kubectl get sc.
Ingress / TLS not working
Ensure cert-manager is installed and a valid ClusterIssuer (letsencrypt-prod) exists:
kubectl get clusterissuer
kubectl describe clusterissuer letsencrypt-prod
Check the ingress status:
kubectl -n gameplane-system get ingress gameplane
Next steps
After a successful install:
- Create a server — deploy your first game server
- Production installation — HA, TLS, external auth, and monitoring for production clusters
- Upgrades and migrations — plan version upgrades and strategy