gameplane / docs
INSTALL

Install with Helm

Prepare a Kubernetes cluster, choose secure chart values, deploy Gameplane, bootstrap access, and verify the control plane before hosting servers.

v0.216 MIN

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.

Kubernetes 1.28+Gameplane requires Kubernetes 1.28 or later. Confirm your version with kubectl.
Helm 3.13+Helm 3.13 or later is required to pull OCI-packaged charts from the registry.
StorageClassA default StorageClass (any RWO CSI driver). Used for the API database and game server state.
Ingress (optional)An ingress controller (nginx-ingress by default) and cert-manager for TLS termination.
Save your exact Helm values

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 via gamesNamespace)
  • 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:6443 or https://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
Coming in v0.3.0

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:

  1. From the dashboard, Servers → Create a server
  2. Choose a module (e.g., Minecraft)
  3. Name it test-server and click Create
  4. Watch the pod spin up in kubectl get pods -n gameplane-games; the server should reach the Ready state within 1–2 minutes

INSTALL CHECK

01   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
02   kubectl -n gameplane-system get pods && kubectl get crds | grep gameplane
03   Log in, select the cluster, and create a test server to verify the operator reconciles

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: