Helm charts for the DyingStar gaming platform microservices.
| Environment | Namespace | Deploy Method | comment |
|---|---|---|---|
| Production | dyingstar-prod |
repository_dispatch from service repos |
for the production |
| Preprod | dyingstar-preprod |
repository_dispatch from service repos |
for preproduction, so code validated but not yet released |
| Dev Shared | dyingstar-dev-shared |
Manual helm install |
for services used by all developpers, like Postgis |
| Dev Local | dyingstar-dev-local |
Skaffold on minikube | for run the game localy, mainly for developpers |
| Chart | Purpose | Source Repo |
|---|---|---|
godotserver |
Godot multiplayer game server (headless service) | ../DyingStar |
horizon |
Horizon game server (NodePort, high CPU) | ../horizonserver |
service-resourcesdynamic |
Dynamic resource manager API + WebSocket, with PostgreSQL | ../services/resourcesDynamic |
keycloak |
Keycloak identity provider (player auth + Discord IdP) | ../services/keycloak |
livekit |
LiveKit Server (WebRTC SFU + TURN) for voice/video rooms | ../services/livekit |
service-persistence |
Persistence service — ScyllaDB-backed data layer (Rust) | ../services/persistence |
dev-services |
Shared developer infrastructure (PostGIS) | — |
├── .github/
│ ├── copilot-instructions.md
│ └── workflows/
│ ├── deploy-prod.yaml # CD: repository_dispatch → dyingstar-prod
│ └── deploy-preprod.yaml # CD: repository_dispatch → dyingstar-preprod
├── godotserver/ # Helm chart
├── horizon/ # Helm chart
├── service-resourcesdynamic/ # Helm chart
├── keycloak/ # Helm chart
├── livekit/ # Helm chart
├── service-persistence/ # Helm chart
├── dev-services/ # Helm chart (shared dev infra)
├── skaffold.yaml # Local dev orchestration
├── dev.sh # Local dev wrapper script
└── dev-local.conf.example # Config template: Harbor vs local build
Each chart contains:
values.yaml— base (env-neutral) defaultsvalues-prod.yaml— production overridesvalues-preprod.yaml— preprod overridesvalues-dev-local.yaml— local dev overrides (minikube)
Service repos build and push Docker images to Harbor, then trigger this repo's workflows via repository_dispatch:
# From a service repo's GitHub Actions, after pushing an image:
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
https://api.github.com/repos/OWNER/kubernetes/dispatches \
-d '{"event_type":"deploy-prod","client_payload":{"chart":"horizon","image_tag":"abc1234"}}'For preprod, use "event_type": "deploy-preprod".
This query will trigger the deployment on the environment selected.
Kube contexts: this workspace has two kube-contexts —
dyingstar(cluster serving prod and preprod) andminikube(dev-local). Always select the right one before runninghelm/kubectl. The examples below pin it via--kube-context=dyingstar.
It's main used for personn have the management of preprod / prod and have the minikube for develop.
# Production
helm upgrade --install --kube-context=dyingstar -n dyingstar-prod godotserver ./godotserver -f godotserver/values-prod.yaml --set image.tag=<tag>
helm upgrade --install --kube-context=dyingstar -n dyingstar-prod horizon ./horizon -f horizon/values-prod.yaml --set image.tag=<tag>
helm upgrade --install --kube-context=dyingstar -n dyingstar-prod service-resourcesdynamic ./service-resourcesdynamic -f service-resourcesdynamic/values-prod.yaml --set image.tag=<tag>
helm upgrade --install --kube-context=dyingstar -n dyingstar-prod keycloak ./keycloak -f keycloak/values-prod.yaml --set image.tag=<tag>
helm upgrade --install --kube-context=dyingstar -n dyingstar-prod livekit ./livekit -f livekit/values-prod.yaml --set image.tag=<tag>
helm upgrade --install --kube-context=dyingstar -n dyingstar-prod service-persistence ./service-persistence -f service-persistence/values-prod.yaml --set image.tag=<tag>
# Preprod
helm upgrade --install --kube-context=dyingstar -n dyingstar-preprod godotserver ./godotserver -f godotserver/values-preprod.yaml --set image.tag=<tag>
helm upgrade --install --kube-context=dyingstar -n dyingstar-preprod horizon ./horizon -f horizon/values-preprod.yaml --set image.tag=<tag>
helm upgrade --install --kube-context=dyingstar -n dyingstar-preprod service-resourcesdynamic ./service-resourcesdynamic -f service-resourcesdynamic/values-preprod.yaml --set image.tag=<tag>
helm upgrade --install --kube-context=dyingstar -n dyingstar-preprod keycloak ./keycloak -f keycloak/values-preprod.yaml --set image.tag=<tag>
helm upgrade --install --kube-context=dyingstar -n dyingstar-preprod livekit ./livekit -f livekit/values-preprod.yaml --set image.tag=<tag>
helm upgrade --install --kube-context=dyingstar -n dyingstar-preprod service-persistence ./service-persistence -f service-persistence/values-preprod.yaml --set image.tag=<tag>You can also trigger deployments manually from the GitHub Actions UI, providing the chart name and image tag.
We use tools, working all on Linux and Windows.
It permit to have something very close to the preprod and prod and working on same way on different Operating Systems.
- minikube
- Skaffold
- Helm
- Sibling service repos cloned:
../DyingStar— godotserver../horizonserver— horizon../services/resourcesDynamic— service-resourcesdynamic../services/keycloak— keycloak../services/livekit— livekit../services/persistence— service-persistence
- freelens, used to manage pods and deployments in an UI
# Start minikube, the size is important because the docker images can be built inside the minikube
minikube start --disk-size=150g --extra-config=apiserver.service-node-port-range=1024-65535
# Deploy all services
./dev.sh
# Or deploy a single service
./dev.sh horizonSkaffold builds Docker images using Dockerfiles from the sibling repos and deploys via Helm with values-dev-local.yaml into namespace dyingstar-dev-local.
To avoid rebuilding services you haven't modified, you can pull pre-built develop images from Harbor instead of building locally.
Setup (one-time):
Copy the example config file to config file ^_^
cp dev-local.conf.example dev-local.confEdit dev-local.conf — uncomment services you want to pull from Harbor:
# Services to pull from Harbor instead of building locally.
#godotserver
horizon
#service-resourcesdynamic
#keycloak
In this example, horizon will be deployed using the Harbor develop image, while the other two are built from source.
Then run:
./dev.sh # Deploy all services
./dev.sh horizon # Deploy only horizon
./dev.sh godotserver horizon # Deploy specific services
./dev.sh --tail=false # Pass extra args to skaffoldNote: minikube must be able to pull from Harbor. If Harbor requires authentication, create an
imagePullSecretin thedyingstar-dev-localnamespace.
You can also use Skaffold modules directly without the wrapper:
skaffold dev -m godotserver,horizon,service-resourcesdynamic,keycloak,livekit,service-persistence # all local builds
skaffold dev -m horizon # single service
skaffold dev -m godotserver,horizon-harbor,service-resourcesdynamic,keycloak,livekit,service-persistence # mix local + harborCouple scenarii in example, depend on what part you develop in local.
In all scenarii, the godot client need to connect to Horizon running in the minikube.
In dyingstar repository (godot files), edit the file client.ini and replace the IP with the value return with the command: minikube ip.
In my case, it's 192.168.49.2, so I define it:
[network]
websocket_url="ws://192.168.49.2:7040"For this case, we use all develop images. We build anothing in local.
In file dev-local.conf, uncomment all lines (remove the #).
On Linux:
./dev.shOn Windows, set all tools (suffix with -harbor):
skaffold dev -m godotserver-harbor,horizon-harbor,service-resourcesdynamic-harbor,keycloak-harbor,service-persistence-harbor
For this case, you develop only godot, so Horizon, services... are the develop version because we don't modify them.
We must modify some files to allow horizon access the godot server you run locally (inside godot editor with F5):
In file horizon/values-dev-local.yaml, uncomment 3 lines, to have:
extraEnv:
- name: GAME_SERVER_HOST
value: "host.minikube.internal"In file horizon/values.yaml, comments the 3 lines in dependsOn, to have:
# - name: godotserver
# service: godotserver
# port: 8980This mean Horizon not wait godotserver pods up because we not use them in this scenario.
In file dev-local.conf, uncomment only the line godotserver (remove the #).
On Linux:
./dev.shOn Windows, set all tools (suffix with -harbor):
skaffold dev -m godotserver,horizon-harbor,service-resourcesdynamic-harbor,keycloak-harbor
In godot, in menu Debug -> Customize Run Instances..., check enable multiple instances and set to 2.
The second line will be the server, define:
- Launch arguments:
--headless - Feature Flags:
dedicated_server
You can run with F5 key.
In Launch arguments, you can append --log-file /tmp/godot/player.log and --log-file /tmp/godot/server.log for have log files.
After start run with F5 in godot, open Freelens, go in Workloads and pods, you can delete the line starts with horizon-. This will restart Horizon and connect to your Godot server. After 20 - 40 seconds, you can connect to game server from client.
In file dev-local.conf, uncomment only the line horizon (remove the #).
On Linux:
./dev.shOn Windows, set all tools (suffix with -harbor):
skaffold dev -m godotserver-harbor,horizon,service-resourcesdynamic-harbor,keycloak-harbor
NOTE: you can mix this chapter and previous chapter if you made modifications in godotserver and horizon in same time!
TODO: need mofdifications for this case
Shared infrastructure for all developers, deployed on the main cluster:
helm upgrade --install -n dyingstar-dev-shared dev-services ./dev-services --create-namespacePostGIS available via NodePort (default 30432).
- Port: 8980 (headless service)
- Prod replicas: 30
- Port: 7040 (NodePort)
- Prod NodePort: 30000, Preprod NodePort: 30100
- High CPU — requires 20+ cores in production
- Ports: 3001 (HTTP API), 9200 (WebSocket)
- Database: Bundled PostgreSQL (configurable per environment)
- Environment variable
DATABASE_URLis auto-configured from chart values
- Port: 9100 (WebSocket, Rust)
- Database: Bundled ScyllaDB 6.2 (CQL port 9042) with
PasswordAuthenticator - Environment variables
SCYLLA_NODES(ashost:port),SCYLLA_KEYSPACE,SCYLLA_USERNAME,SCYLLA_PASSWORDare auto-configured from chart values - Dev-local: ScyllaDB runs with
--developer-mode 1 --smp 1(no PVC — emptyDir); prod/preprod use a PVC (10Gi/2Gi) - Important: change the default
cassandrasuperuser password in prod/preprod after first deploy via CQL:ALTER USER cassandra WITH PASSWORD '<new-strong-password>';
- Ports: 8080 (HTTP), 9000 (management/health/metrics)
- Database: Bundled PostgreSQL (single-pod, mirrors
service-resourcesdynamic) - Hostnames:
auth.dyingstar-game.com(prod),auth-preprod.dyingstar-game.com(preprod), NodePort30180(dev-local) - Realm:
dyingstar— imported on every start from the JSON baked into the image - Discord IdP is registered/updated by a Helm post-install Job (
kcadm.shscript shipped in../services/keycloak) - Required Secrets (operator-managed in prod/preprod, inlined in
values-dev-local.yamlfor local dev):keycloak-admin— keysKEYCLOAK_ADMIN,KEYCLOAK_ADMIN_PASSWORDkeycloak-discord— keysDISCORD_CLIENT_ID,DISCORD_CLIENT_SECRET
- Discord OAuth callback URLs to register on the Discord developer portal:
- prod:
https://auth.dyingstar-game.com/realms/dyingstar/broker/discord/endpoint - preprod:
https://auth-preprod.dyingstar-game.com/realms/dyingstar/broker/discord/endpoint - local:
http://<minikube-ip>:30180/realms/dyingstar/broker/discord/endpoint
- prod:
Create the prod/preprod secrets with:
kubectl --context=dyingstar -n dyingstar-prod create secret generic keycloak-admin \
--from-literal=KEYCLOAK_ADMIN=admin \
--from-literal=KEYCLOAK_ADMIN_PASSWORD='<strong-password>'
kubectl --context=dyingstar -n dyingstar-prod create secret generic keycloak-discord \
--from-literal=DISCORD_CLIENT_ID='<id>' \
--from-literal=DISCORD_CLIENT_SECRET='<secret>'| Secret | Purpose |
|---|---|
KUBE_CONFIG |
Base64-encoded kubeconfig for the target cluster |
Service repos also need a KUBERNETES_REPO_TOKEN (GitHub PAT) to trigger repository_dispatch.
This part is for repository admin, becasue the token is only for 1 year, need to renew and so configure again on same way!
KUBERNETES_REPO_TOKEN is a GitHub Personal Access Token (PAT) that allows the service repo to trigger repository_dispatch on the kubernetes repo.
- Go to GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens
- Click Generate new token
- Set:
- Token name: e.g.
deploy-preprod-dispatch - Expiration: your preference
- Resource owner: your org (the one owning the kubernetes repo)
- Repository access: select Only select repositories → pick the kubernetes repo
- Permissions → Repository permissions:
- Contents: Read
- Metadata: Read (auto-selected)
- Token name: e.g.
- Generate and copy the token
- Add it as an organization-level secret named
KUBERNETES_REPO_TOKEN(Settings → Secrets and variables → Actions → Organization secrets)
The peter-evans/repository-dispatch action in build-preprod.yaml uses this token to POST the deploy-preprod event to the kubernetes repo's API.