Skip to content

Instantly share code, notes, and snippets.

@ryanditjia
Last active September 28, 2026 16:04
Show Gist options
  • Select an option

  • Save ryanditjia/dfe63fa9e88ecb14ac4bc223d746e9b8 to your computer and use it in GitHub Desktop.

Select an option

Save ryanditjia/dfe63fa9e88ecb14ac4bc223d746e9b8 to your computer and use it in GitHub Desktop.
ADR-066 simplified

pkg/k8s Manifest Composition Plan

Goal

Keep Kubernetes generation simple and composable.

The core rule:

Manifests() runs the World Engine game. It does not automatically provision the whole surrounding platform.

That means:

world.toml
+
deployment options
    ↓
pkg/k8s.Manifests(...)
    ↓
game workload Kubernetes objects

NATS and Postgres remain optional building blocks that callers can compose around the game.


1. pkg/k8s.Manifests

Proposed API shape:

func Manifests(
    world toml.World,
    opts ManifestOptions,
) ([]client.Object, error)

Exact types can change, but the responsibility should stay narrow.

Inputs

Likely inputs include:

type ManifestOptions struct {
    Namespace string

    Images map[string]ImageRef

    NATSURL string
    DBDSN   string

    AuthMode    string
    ArgusAuthURL string

    // Optional routing/deployment details.
    Ingress *IngressOptions
}

This is illustrative, not final API design.


2. What Manifests() Owns

Manifests() should produce the Kubernetes resources needed to run the game's World Engine workloads.

Likely responsibilities:

  • shard Deployments
  • shard Services
  • pool-size / instance expansion
  • workload names
  • labels and selectors
  • container image references
  • container ports
  • resource requests and limits
  • CARDINAL_* runtime environment variables
  • NATS_URL
  • DB_DSN for DB-backed shards
  • authentication-related environment
  • optional Ingress, if public routing is considered part of generic World Engine deployment

Conceptually:

world.toml
    +
images
NATS URL
DB DSN
auth config
namespace
    ↓
Manifests()
    ↓
Deployment
Deployment
Service
Service
Ingress? 
...

3. What Manifests() Does Not Own

Do not put these inside Manifests():

  • creating Kubernetes namespaces
  • installing NATS
  • installing Postgres
  • installing an OpenTelemetry Collector
  • ArgoCD configuration
  • Git operations
  • GitHub Actions behavior
  • ECR queries
  • image-tag selection policy
  • branch names
  • ephemeral-environment TTL
  • environment owner
  • automatic reaping
  • concurrency limits
  • AWS-specific deployment policy

The function should not need access to:

GitHub
AWS
ArgoCD
a Kubernetes cluster
a container registry
Git

It should only transform inputs into Kubernetes objects.


4. Optional Infrastructure Helpers

NATS and Postgres can still live in pkg/k8s.

They should just be separate composition helpers.

For example:

func NATSManifests(opts NATSOptions) ([]client.Object, error)

func PostgresManifests(opts PostgresOptions) ([]client.Object, error)

Possible structure:

pkg/k8s/
  manifests.go
  deployment.go
  service.go
  ingress.go
  env.go

  nats.go
  postgres.go

Avoid creating subpackages until there is enough complexity to justify them.


5. Local Development

Local development should remain batteries-included.

world start should compose the helpers automatically.

Conceptually:

objects := []client.Object{}

objects = append(
    objects,
    k8s.NATSManifests(k8s.NATSOptions{
        Namespace: localNamespace,
    })...,
)

if world.NeedsDB() {
    objects = append(
        objects,
        k8s.PostgresManifests(k8s.PostgresOptions{
            Namespace: localNamespace,
        })...,
    )
}

objects = append(
    objects,
    k8s.Manifests(world, k8s.ManifestOptions{
        Namespace: localNamespace,
        Images:    images,
        NATSURL:   localNATSURL,
        DBDSN:     localDBDSN,
        AuthMode:  "DEV",
    })...,
)

apply(objects)

From the user's perspective:

world start

still just works.

The fact that NATS/Postgres are separate helpers is an implementation detail.


6. Remote Deployment

Remote deployment should default to using infrastructure supplied by the environment.

Example:

managed/existing NATS
        │
        └── NATS_URL

managed/existing Postgres
        │
        └── DB_DSN

             ↓

       k8s.Manifests()

             ↓

        game workloads

This keeps World Engine compatible with:

  • an existing NATS installation
  • managed Postgres
  • platform-owned databases
  • an organisation's own observability stack
  • any Kubernetes distribution

World Engine does not need to install infrastructure that the organisation already has.


7. Rampage Ephemeral Environments

Rampage may still want per-environment NATS and Postgres.

That is fine.

ephemeralgen can compose the same helpers:

objects := []client.Object{}

objects = append(
    objects,
    k8s.NATSManifests(... )...,
)

if world.NeedsDB() {
    objects = append(
        objects,
        k8s.PostgresManifests(... )...,
    )
}

objects = append(
    objects,
    k8s.Manifests(world, k8s.ManifestOptions{
        Namespace: namespace,
        Images:    selectedImages,
        NATSURL:   generatedNATSURL,
        DBDSN:     generatedDBDSN,
        AuthMode:  selectedAuthMode,
    })...,
)

Rampage-specific deployment logic remains outside pkg/k8s:

branch → env slug
TTL
owner
image selection
ECR lookup
ArgoCD registration
ephemeral-envs branch
environment reaping

8. Why This Boundary Matters

Avoid this:

Manifests()
  ├─ create Namespace
  ├─ create NATS
  ├─ create Postgres
  ├─ create OTel
  ├─ create secrets
  ├─ create workloads
  ├─ decide ingress
  └─ effectively become a platform installer

Prefer:

NATSManifests() ───────┐
                       │
PostgresManifests() ───┼─ caller composes
                       │
Manifests() ───────────┘

This gives:

  • simple local DX
  • flexible remote deployment
  • no hidden platform assumptions
  • reusable infrastructure pieces
  • easier testing
  • easier future removal/replacement of individual dependencies

9. Namespace Ownership

Manifests() should accept a namespace but should probably not create the Namespace object itself.

For example:

ManifestOptions{
    Namespace: "rampage-eph-feature-x",
}

Why:

  • local world start already knows when to create the local namespace
  • Rampage ephemeral tooling owns ephemeral namespace lifecycle
  • platform teams may pre-create namespaces with RBAC, quotas, policies, secrets, etc.
  • ArgoCD may manage namespace creation separately

Namespace lifecycle is deployment policy, not game workload generation.

If useful, a separate helper could exist:

func NamespaceManifest(name string) client.Object

but it should not be implicit.


10. NATS Ownership

NATSManifests() should be a convenience helper, not a requirement.

Possible generated resources:

Deployment / StatefulSet
Service
ConfigMap
Secret, if required

The output should give the caller the URL to pass to the game:

nats://nats:4222

or the caller derives it from stable naming.

Manifests() only needs:

NATSURL: "nats://nats:4222"

It should not care whether that NATS came from:

  • NATSManifests()
  • a Helm chart
  • a platform-wide NATS cluster
  • managed infrastructure
  • another namespace

11. Postgres Ownership

Same principle.

PostgresManifests() is an optional convenience helper.

Possible generated resources:

StatefulSet / Deployment
Service
Secret
PVC

It can expose or return the DSN:

postgres://...

Manifests() only receives:

DBDSN: "postgres://..."

and injects it into shards that require a database.

It should not know how the database was provisioned.


12. OTel / Observability

Follow the same pattern.

Prefer:

Manifests()
  → receives OTEL_EXPORTER_OTLP_ENDPOINT

Do not automatically create an OTel Collector unless there is an explicit convenience helper:

func OTelCollectorManifests(opts OTelOptions) ([]client.Object, error)

Local or Rampage environments can choose to compose it.

External deployments can point directly at their existing collector.


13. Desired End State

                        world.toml
                            │
                            ▼
                   pkg/k8s.Manifests()
                            │
                            ▼
                   game K8s workloads
                   /                 \
                  /                   \
         world start              remote deploy
             │                         │
             │                         │
   NATSManifests() optional    platform NATS or
   PostgresManifests()         NATSManifests()
             │                         │
             ▼                         ▼
         local k3d                 Kubernetes

The important rule:

Manifests() knows how to run the game. The caller decides what infrastructure surrounds it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment