The rig runs the entire Enterprise MVP stack on your laptop, no GPU
required: a kind cluster with ingress, a fake OpenAI-compatible engine
standing in for the inference runtime, and the full platform (gateway,
identity, control plane, portal, chat) installed as one Helm release from
the umbrella chart — including the wiring job that connects everything
(region registration, OIDC clients, service tokens). It is the same install
path a customer uses (TKF-27/29/44), so if it works here, the product works.
Everything lives in dev/kind/ in the main repo; entry points are make
targets at the repo root.
kind, kubectl, helm, Node 20+ (for portal image builds)file://~/git/mirantis/tokenfactory/
├── tokenfactory/ # main repo (you run make from here)
├── tokenfactory-controlplane/
├── tokenfactory-portal/
├── tokenfactory-identity/
├── tokenfactory-gateway/
└── tokenfactory-librechat/
No /etc/hosts edits needed — *.tf.localtest.me resolves to 127.0.0.1
by DNS, and the kind cluster maps port 80 to the ingress controller.
make dev-up # kind cluster 'tokenfactory' + ingress-nginx + fake engine
The fake engine (dev/kind/fake-engine.yaml, namespace tf-dev) speaks the
runtime↔gateway contract: OpenAI-compatible base URL
http://fake-engine.tf-dev.svc.cluster.local:8000/v1, model fake-7b. It
echoes prompts back, so you can always tell a real round-trip from a cached
response.
Two modes:
main in the component repos buildsghcr.io/mirantis/tokenfactory-*. UseIMAGES=ghcr ./dev/kind/make-tf-values.sh; private packages need aghcr-pull secret in the tf namespacekubectl create secret docker-registry ghcr-pull --docker-server=ghcr.io --docker-username=<user> --docker-password=<PAT with read:packages>).tf-<component>:dev, the default) — for iterating on componenttokenfactory-portal/specs/; locally you can too, or use live# Control plane (from tokenfactory-controlplane/)
docker build -t tf-controlplane-backend:dev .
# Identity (from tokenfactory-identity/) — Matomo args are required-but-dummy
docker build -f .lagoon/keycloak/Dockerfile -t tf-identity-keycloak:dev \
--build-arg VITE_MATOMO_URL=https://matomo.invalid \
--build-arg VITE_MATOMO_SITE_ID=0 \
--build-arg VITE_MATOMO_COOKIE_DOMAIN=tf.localtest.me .
docker build -f .lagoon/php/Dockerfile --target php -t tf-identity-api-php:dev .
docker build -f .lagoon/php/Dockerfile --target nginx -t tf-identity-api-nginx:dev .
docker build -f .lagoon/php/Dockerfile --target cli -t tf-identity-cli:dev .
Load everything you built into the cluster (kind does not see your local
Docker images otherwise):
kind load docker-image -n tokenfactory \
tf-controlplane-backend:dev \
tf-identity-keycloak:dev tf-identity-api-php:dev tf-identity-api-nginx:dev tf-identity-cli:dev
make dev-stack
This generates dev/kind/tf-values.yaml (rig-local secrets, gitignored),
resolves the umbrella chart's dependencies, and runs
helm upgrade --install tf charts/tokenfactory -n tf. Helm returns after
the wiring job succeeds — when it exits 0, the stack is connected end to
end. Watch it with:
kubectl logs -n tf job/tokenfactory-wiring -f
The portal image build needs the control plane and identity APIs running
(codegen against their live OpenAPI specs), so on a first bring-up the
portal pods sit in ErrImageNeverPull until you build them — the rest of
the stack and the wiring job come up fine. Once the stack is running:
# terminal 1 — spec port-forwards
kubectl -n tf port-forward svc/controlplane-backend 18800:8800 &
kubectl -n tf port-forward svc/identity-api 18801:8080 &
# terminal 2 — from tokenfactory-portal/
CPTOK=$(kubectl get secret -n tf portal-secrets -o jsonpath='{.data.controlplane-token}' | base64 -d)
docker build -f Dockerfile.api -t tf-portal-api:dev \
--build-arg AMAZEEAI_ADMIN_API_URL=http://host.docker.internal:18800 \
--build-arg AMAZEEAI_ADMIN_API_TOKEN="$CPTOK" \
--build-arg MOAD_AUTH_API_URL=http://host.docker.internal:18801 \
--build-arg POLYDOCK_API_URL=https://nginx.prod.polydock-engine.ch4.amazee.io/api .
docker build -f Dockerfile.frontend -t tf-portal-frontend:dev \
--build-arg VITE_BILLING_UI=false \
--build-arg AMAZEEAI_ADMIN_API_URL=http://host.docker.internal:18800 \
--build-arg AMAZEEAI_ADMIN_API_TOKEN="$CPTOK" .
kind load docker-image -n tokenfactory tf-portal-api:dev tf-portal-frontend:dev
kubectl -n tf rollout restart deploy/portal-api deploy/portal-frontend
| URL | What | Login |
|---|---|---|
| http://portal.tf.localtest.me | Portal | seed a user in Keycloak (self-registration off by default since TKF-76) |
| http://chat.tf.localtest.me | Chat | sign in with the same Keycloak user (SSO) |
| http://gateway.tf.localtest.me | Gateway API + /ui admin |
UI: admin / master key sk-tf-dev-master |
| http://identity.tf.localtest.me | Keycloak | admin console: admin / tf-dev-admin |
| http://engine.tf.localtest.me | Fake engine | none |
Control plane admin (API only): admin@tokenfactory.dev / tf-dev-admin-pw
— created by the wiring job.
The proof-of-life loop: log into the portal → create a key in the
rig-local region → call it from your laptop:
curl http://gateway.tf.localtest.me/v1/chat/completions \
-H "Authorization: Bearer <your key>" -H 'Content-Type: application/json' \
-d '{"model":"fake-7b","messages":[{"role":"user","content":"ping"}]}'
An echo reply from fake-engine means the whole governed path works:
portal → control plane → gateway → engine.
Rebuild the image, load it, restart the deployment — no Helm needed:
docker build -t tf-<component>:dev <repo> # see build commands above
kind load docker-image -n tokenfactory tf-<component>:dev
kubectl -n tf rollout restart deploy/<deployment>
Deployments: controlplane-backend, identity-keycloak, identity-api,
portal-api, portal-frontend, chat, gateway-litellm. For chart
changes, run make dev-stack again — the wiring job reruns on every upgrade
and is idempotent (it only patches what drifted).
ErrImageNeverPull — you built the image but forgotkind load docker-image.localStorage (persisted query cache) from a previous rig installportal.tf.localtest.me.prisma db push in the proxy pod (gateway chart README).kubectl logs -n tf job/tokenfactory-wiring. Safe to rerun viamake dev-stack.subPath changes not appearing —subPath mounts only refresh on pod restart.helm uninstall tf -n tf # just the stack (keeps cluster + fake engine)
make dev-down # everything, including the kind cluster