Skip to content

Rebuild local infra as one Minikube cluster per app with provider namespaces #32

Description

@artiphishle

Objective

Rebuild local generated infrastructure so one app owns one complete Minikube cluster, named by the canonical app slug, and all app-owned infrastructure runs inside that cluster in clean Kubernetes namespaces.

Current unwanted model:

Docker host
├── minikube                 # shared by all apps
└── <slug>                   # Supabase CLI / Compose group
    ├── supabase_db_<slug>
    ├── supabase_auth_<slug>
    ├── supabase_storage_<slug>
    └── ...

Target model:

Docker host
└── <slug>                   # Minikube profile / app-owned cluster
    └── Kubernetes
        ├── namespace: app
        │   └── app-runtime
        ├── namespace: supabase
        │   ├── postgres
        │   ├── auth
        │   ├── rest
        │   ├── storage
        │   ├── realtime
        │   ├── gateway
        │   ├── studio
        │   └── other required Supabase workloads
        └── namespace: <other-infra-adapter>
            └── adapter-owned workloads

A second app creates a second isolated cluster:

Docker host
├── uwm
│   ├── namespace: app
│   └── namespace: supabase
└── chess
    ├── namespace: app
    └── namespace: supabase

The cluster is the app ownership boundary. Kubernetes namespaces are the provider/adapter ownership boundaries inside the app.

Hard requirements

  • No legacy path.
  • No deprecation layer.
  • No compatibility aliases or fallback behavior.
  • Remove the shared default Minikube profile minikube.
  • Remove host-level Supabase runtime ownership through supabase start / supabase stop.
  • Do not leave a hybrid architecture with app runtime in Kubernetes and Supabase beside it in Docker Compose.
  • Canonical app slug is the Minikube profile identity.
  • Generated app runtime belongs in namespace app.
  • Supabase-owned workloads belong in namespace supabase.
  • Future self-hosted infrastructure adapters own their own deterministic namespaces.
  • External SaaS such as Stripe is not deployed as local provider containers; only Ankhorage-owned integration workloads, if any, belong in an internal namespace.
  • Update every downstream dependency and consumer. No stale generated scripts, docs, package versions, or assumptions.
  • Strict TypeScript only: no any, no ts-ignore, no ESLint disables, no weakening strictness.
  • One complete PR per affected repository. Do not auto-merge.

Current code paths to replace

Search the entire Ankhorage repository set for all dependencies on:

  • MINIKUBE_PROFILE
  • literal profile minikube
  • supabase start
  • supabase stop
  • supabase status
  • SUPABASE_PROJECT_DIR
  • host-level Supabase port allocation
  • supabase_*_<slug> container assumptions
  • current Kubernetes namespace assumptions
  • Infra Up/Down/Status APIs and scripts

Relevant Infra areas include at least:

  • src/adapters/minikube/index.ts
  • src/adapters/minikube/base/index.ts
  • generateMinikubeInfra
  • generateMinikubeBaseArtifacts
  • getUpScript
  • getDownScript
  • getStatusScript
  • getSupabaseLocalEnvScript
  • generated infra/minikube/**

Do not assume this list is exhaustive.

1. Cluster naming and lifecycle

For app slug uwm, all Minikube operations must target:

minikube -p uwm ...

For app slug chess:

minikube -p chess ...

The slug must consistently drive:

  • start
  • status
  • stop
  • destructive cleanup
  • kubectl context
  • image load/build
  • port-forward
  • diagnostics
  • generated docs

No command for one app may target another app's cluster.

2. Namespace model

Minimum namespaces:

app
supabase

The architecture must support later adapter-owned namespaces such as:

payments
monitoring
search
messaging

Do not use the app slug as the Kubernetes namespace just because the cluster used to be shared. The slug now identifies the whole cluster.

3. Kubernetes-native Supabase runtime

Remove host-level Supabase Compose runtime from generated Infra Up. supabase start must no longer own the local runtime.

Implement the selected local Supabase stack as Kubernetes workloads in namespace supabase.

Before implementation, Codex must check the current Supabase changelog and official self-hosting/deployment documentation. Do not copy stale image topology or environment configuration from memory.

Define one canonical source for:

  • pinned Supabase images/versions
  • Postgres
  • Auth
  • PostgREST
  • Storage
  • Realtime
  • gateway/API routing
  • Studio if included
  • analytics/vector/metadata/edge runtime when required
  • Secrets and ConfigMaps
  • persistent volumes
  • readiness/liveness
  • service discovery
  • startup order

Do not blindly reproduce every container from supabase start if the official self-hosted topology or Ankhorage capability set does not require it.

After Infra Up there must be no host-level runtime containers named like supabase_*_<slug>.

4. Networking

Explicitly separate:

  1. in-cluster service URLs
  2. host/browser-accessible development URLs

Example:

app workload -> Supabase gateway through Kubernetes DNS
browser/host -> localhost:<project-specific-port> -> exposed Supabase gateway

Two app clusters must run simultaneously without port collisions.

Choose one canonical exposure mechanism; do not add multiple compatibility fallbacks.

Must remain reachable where applicable:

  • Supabase API/Auth gateway
  • Storage
  • Studio
  • generated app runtime

OAuth PKCE/deep-link callbacks must be revalidated.

Never expose service-role/secret credentials to the browser. Public app env receives only the public URL and appropriate public/anon/publishable key.

5. Persistence and database lifecycle

Supabase Postgres must run inside the app cluster under namespace supabase.

Define explicit behavior for:

  • Infra Up
  • Infra Down
  • restart
  • app deletion
  • destructive reset

Ordinary Down must not accidentally destroy persistent app data unless that is the explicitly documented contract. Destructive cleanup must target only the selected app cluster.

6. Preserve migrations and Auth functionality

The new lifecycle must preserve this sequence:

  1. start/create app cluster
  2. deploy Supabase namespace workloads
  3. wait for database readiness
  4. apply immutable migrations
  5. apply generated profile reconciliation when enabled
  6. verify disabled-profile state when disabled
  7. apply/verify Supabase Vault integration when selected
  8. verify schema
  9. resolve public/runtime endpoints and keys
  10. wire generated app env/config
  11. build/load/deploy application

Must continue to work:

  • email/password sign-up
  • email/password sign-in
  • persisted sessions
  • OAuth PKCE when configured
  • auth.users
  • generated public.profiles
  • Storage
  • Supabase Vault

The Supabase CLI may still be used as a migration/development tool where appropriate, but never as owner of a separate host-level runtime stack.

7. App namespace

Move generated application Kubernetes resources to namespace app:

  • Deployment
  • Service
  • ConfigMaps
  • Secrets
  • image pull secrets
  • rollout/scaling
  • status
  • port-forward

Update every generated kubectl -n ... command accordingly.

8. Rewrite Infra lifecycle

Infra Up

resolve canonical slug
-> ensure Minikube profile <slug> is running
-> select <slug> kubectl context
-> create required namespaces
-> deploy adapter-owned infrastructure
-> wait for readiness
-> run migrations/reconciliation
-> expose required local endpoints
-> resolve public/runtime env
-> build/load app image
-> deploy app namespace
-> wait for readiness
-> report URLs and health

Infra Status

Must report only the target app:

  • Minikube profile state
  • app namespace health
  • supabase namespace health
  • database connectivity
  • migration/profile state
  • endpoint availability
  • required config/secrets

Do not use host-level supabase status as runtime detection.

Infra Down / Delete

Operate only on minikube -p <slug>. Stopping/deleting uwm must never affect chess.

9. Delete obsolete host-stack code

Remove, not deprecate:

  • generated supabase start
  • generated supabase stop --no-backup
  • unconditional stale-stack retry
  • host-level Supabase runtime detection
  • host-level per-app Compose ownership
  • obsolete port-preflight logic
  • obsolete SUPABASE_PROJECT_DIR runtime ownership assumptions
  • docs/tests for the old architecture

If a Supabase project directory remains useful for migrations/config authoring, clearly separate authoring from runtime ownership.

10. Adapter composition

The architecture must generalize as:

<app-slug> cluster
├── app
├── <infra-adapter-a>
├── <infra-adapter-b>
└── ...

Each self-hosted adapter may contribute only what it owns:

  • namespace
  • Kubernetes resources
  • dependencies/order
  • readiness
  • public endpoint requirements
  • Secrets/ConfigMaps

Keep this model minimal and deterministic. Do not invent a large new plugin framework unless the existing architecture already has the correct extension point.

11. Downstream dependency updates

This issue is not complete when only @ankhorage/infra compiles.

Inspect and update at minimum:

ankhorage/infra

Primary implementation, tests, docs, changeset.

ankhorage/studio

Update released @ankhorage/infra dependency plus any host assumptions, status parsing, port-forward/runtime diagnostics, generated paths and smoke tests. No fallback to old Infra behavior.

ankhorage/templates

Verify generated apps consume the new endpoint/env contract; update only as required.

ankhorage/contracts

Change only if the canonical manifest genuinely needs explicit new adapter/namespace/runtime metadata. Do not add compatibility fields.

ankhorage/ankh, orchestrator, CLI and other consumers

Search for all Minikube profile, Infra lifecycle, generated file and runtime URL assumptions.

For every published repo changed:

  • complete PR
  • Changeset
  • release in dependency order
  • downstream dependency bump only after upstream release
  • lockfile update
  • full validation

Final implementation must work with released packages, not only local workspace links.

Codex execution phases

Phase A — inventory and design proof

Before editing:

  1. map Studio -> Infra -> generated scripts -> Minikube/Supabase current call chain
  2. search all repos for old lifecycle assumptions
  3. check current official Supabase changelog/docs
  4. identify required self-hosted Supabase workload graph
  5. define namespaces
  6. define startup/readiness order
  7. define host/browser endpoint exposure and collision avoidance
  8. define persistence semantics

Document this map in the Infra PR description.

Phase B — complete Infra rewrite

Implement the new architecture fully in ankhorage/infra. Do not create a halfway hybrid state.

Phase C — release Infra

Run gates, review, merge, release new @ankhorage/infra version.

Phase D — update consumers

One complete PR per affected repo, in dependency order, consuming released upstream versions.

Phase E — clean-state end-to-end proof

Create uwm, Infra Up, then create chess, Infra Up.

Expected Docker-level ownership:

uwm
chess

Not:

minikube
uwm -> host Supabase Compose containers
chess -> host Supabase Compose containers

Inside each cluster, verify namespaces and pods independently.

Then prove lifecycle isolation:

Down uwm -> chess remains healthy
Up uwm   -> chess unchanged
Delete/reset uwm -> chess unchanged

Required acceptance criteria

Cluster ownership

  • one Minikube profile per app slug
  • no shared default minikube cluster
  • two apps can run simultaneously
  • lifecycle commands always target correct profile

Namespace ownership

  • app namespace exists
  • supabase namespace exists when selected
  • Supabase workloads live only in supabase
  • app runtime lives only in app

Runtime ownership

  • no host-level Supabase Compose runtime
  • no supabase_*_<slug> host runtime containers
  • no generated supabase start
  • no stale-stack retry path

Supabase/Auth

  • database ready
  • Auth ready
  • REST/Data API ready
  • Storage/Reatime where selected
  • Studio reachable if included
  • immutable migrations idempotent
  • profile reconciliation works
  • disabled-profile verification works
  • Vault integration works
  • email/password sign-up works
  • email/password sign-in works
  • session persistence works
  • OAuth PKCE works when configured

Security/env

  • browser receives only public URL/public key material
  • privileged credentials remain server/cluster-side
  • Kubernetes Secrets used appropriately
  • no secrets committed

App runtime

  • Expo web export builds
  • image loads into correct profile
  • app Deployment ready in app
  • app reaches its own Supabase stack
  • app cannot accidentally use another app's Supabase stack

Diagnostics/performance

  • Status reports actual cluster/namespace workload health
  • unhealthy workload exposes real readiness/log context
  • idempotent Up does not recreate healthy cluster
  • second app does not rebuild unrelated app resources

Automated tests

Add focused tests for at least:

  1. slug -> Minikube profile
  2. no shared profile default
  3. app and supabase namespace generation
  4. no generated supabase start
  5. no host stale-stack retry
  6. Up/Down/Status target -p <slug> only
  7. distinct profiles for two apps
  8. adapter namespace composition
  9. networking/public endpoint generation
  10. secrets/public env separation
  11. migration/profile/Vault ordering
  12. cleanup isolation

Add opt-in integration tests for actual Minikube/Kubernetes/Supabase startup where CI cost is too high, but document and run them for final verification.

Validation gates

Run canonical gates in every modified repo, including where available:

bun run build
bun run test
bun run typecheck
bun run lint
bun run format:check
bun run knip
bun run docs

Also run Changeset/package validation and npm pack --dry-run for published packages where appropriate.

Do not claim completion from unit tests alone.

Definition of done

Complete only when:

  1. uwm creates Minikube profile/container uwm.
  2. uwm contains namespaces app and supabase.
  3. Supabase workloads run inside supabase.
  4. No separate host Supabase Compose group exists for uwm.
  5. chess creates a separate independent Minikube cluster.
  6. Up/Down/Status/Delete are isolated by slug.
  7. Fresh generated app sign-up/sign-in works.
  8. Existing migrations, profile reconciliation, Vault, Storage and OAuth are verified.
  9. Every affected repo consumes the released dependency chain.
  10. All old shared-cluster/host-Supabase runtime code is removed, not deprecated.
  11. Documentation describes only the new architecture.
  12. Final clean-state report includes Docker containers, Minikube profiles, namespaces, pods, endpoints, auth proof and lifecycle-isolation proof.

Codex final report

Explicitly report:

  • repositories changed
  • PRs created
  • released versions/dependency order
  • old architecture removed
  • new cluster/namespace ownership model
  • clean-state commands used
  • Docker state after one app and two apps
  • namespace/pod listings
  • proof no host-level supabase_*_<slug> runtime containers remain
  • sign-up/sign-in proof
  • isolation proof between two apps
  • all validation results
  • any remaining limitation stated explicitly

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions