A one-command local Kubernetes sandbox for macOS — kind + Argo CD + Kargo +
Istio + an in-cluster Docker registry + an in-cluster Gitea, all wired up
behind a single wildcard cert and a stable *.sandbox.app:8443 URL.
sandboxctl up brings the platform up. sandboxctl deploy deploys your
app into it via a real GitOps loop — chart pushed to in-cluster Gitea, Argo
CD syncs, Istio routes the URL.
sandboxctl-demo-readme.mp4
A full sandboxctl up run, end to end. If the player above doesn’t load,
▶ open the demo video
(MP4, ~500 MB — hosted on the v2.3.0 release).
brew install tesserix/tap/sandboxctlThat's the only step. The formula installs the binary plus the bundled
sandbox.sh and Helm manifests it drives.
From your product repo root (the dir that holds your Dockerfile, chart,
and k8s/secrets.yaml):
cd path/to/your-app
sandboxctl bootstrap # brings the platform up + deploys your appbootstrap is the one-shot wrapper. It runs sandboxctl up (skipped if
the cluster is already up) then sandboxctl deploy in the current dir,
so re-running it later is also fine as your "redeploy this app"
shortcut.
If you'd rather run the steps separately:
sandboxctl up # one-time: bring the platform up (~5–8 min first run)
cd path/to/your-app
sandboxctl deploy # build + push images, deploy via Argo CDOpen https://<your-chart-name>.sandbox.app:8443 in your browser.
A plain sandboxctl up (no flags) is intentionally small — it only stands
up the pieces you'd reach for on day one. Everything else is opt-in
behind a --with-* flag. Pass --install all for the kitchen-sink demo.
| Component | URL / port | What it gives you |
|---|---|---|
| Argo CD | https://argo.sandbox.app:8443 |
GitOps controller (the cluster's source of truth) |
| Kargo | https://kargo.sandbox.app:8443 |
Promotion pipelines on top of Argo |
| Demo app | https://demo-app.sandbox.app:8443 |
A tiny deployment so the cluster ships with something live |
| Gitea | https://gitea.sandbox.app:8443 |
In-cluster git server backing sandboxctl deploy |
| In-cluster registry | localhost:5050 (NodePort 30050) |
Push target for sandboxctl build / docker push |
| dnsmasq + DNS | /etc/resolver/sandbox.app → 127.0.0.1 |
Wildcard *.sandbox.app resolution on the host |
| Plumbing | — | cert-manager + per-install root CA (trusted in System keychain), reflector, reloader, Istio ambient + ingress, kubectl port-forward LaunchAgent |
All TLS is signed by a per-install root CA — no browser warnings, no
manual port-forwards. sandboxctl creds prints admin passwords on
demand; each install generates its own and nothing is hard-coded.
Pass the flag to sandboxctl up (or bootstrap); flags are forwarded
through, and up is idempotent so re-running with a new flag adds the
piece without disturbing the rest.
| Component | Flag | URL / port | What it gives you |
|---|---|---|---|
| arctl (CLI on the Mac) | --with-arctl |
binary in /usr/local/bin/arctl |
agentregistry CLI — build/publish/run MCP servers, agents, skills, prompts. Pin with ARCTL_VERSION. SANDBOX_KEEP_ARCTL=1 keeps it on down/purge. |
| CloudNativePG operator | --with-cnpg |
(operator only) | Postgres-as-a-CRD; foundation for shared Postgres clusters. Implied by --with-agentregistry. |
| agentregistry server | --with-agentregistry |
https://aregistry.sandbox.app:8443 |
In-cluster registry for MCP/agents/skills, backed by a CNPG Postgres with pgvector (semantic search). Implies --with-cnpg. |
| NATS + JetStream | --with-nats |
nats://nats.sandbox.app:4222 (also wss://nats.sandbox.app:8443) |
Pub/sub + persistent streams. Adds a LaunchAgent that port-forwards :4222 to the host. Pair with --no-nats-cli to skip the host-side nats CLI install. |
| kagent | --with-kagent |
https://kagent.sandbox.app:8443 |
kagent — agentic AI controller + UI (controller-only install; configure model providers out-of-band). |
| agentgateway | --with-agentgateway |
https://agentgateway.sandbox.app:8443 |
agentgateway — Linux-Foundation-hosted, Gateway-API-native proxy for AI traffic (MCP / A2A / agent-to-LLM). Drop in HTTPRoutes from any namespace. |
| Portkey | --with-portkey |
https://portkey.sandbox.app:8443 (console /public/) |
OSS AI gateway: routing, retries, fallbacks, load-balancing for 250+ LLMs. |
| LiteLLM | --with-litellm |
https://litellm.sandbox.app:8443 (UI /ui) |
OpenAI-compatible proxy for 100+ providers. Reuses the shared CNPG cluster when --with-agentregistry (or --with-cnpg) is also on; otherwise falls back to the chart's bundled standalone Postgres. |
| MLflow | --with-mlflow |
https://mlflow.sandbox.app:8443 |
Experiment tracking + model registry UI. |
| Tyk OSS | --with-tyk |
https://tyk.sandbox.app:8443 (/hello) |
Open-source API gateway — rate-limit/auth/quotas in front of any upstream. (Bundled Redis is sandbox-grade: single replica, no PVC.) |
| All AI gateways at once | --with-ai-gateway |
— | Umbrella: enables agentgateway + Portkey + LiteLLM + MLflow + Tyk. |
| Kitchen sink | --install all |
— | Every opt-in above (full demo install). |
sandboxctl up # core only
sandboxctl up --with-agentgateway # add agentgateway
sandboxctl up --with-agentregistry --with-arctl # agentregistry + CLI (pulls in CNPG)
sandboxctl up --with-litellm # LiteLLM with the chart's standalone Postgres
sandboxctl up --with-cnpg --with-litellm # LiteLLM on a shared CNPG cluster (no agentregistry)
sandboxctl up --with-ai-gateway # all five AI gateways
sandboxctl up --install all # everything
sandboxctl creds # URLs, admin creds, master keys, copy-paste curl examplesagentgateway is configured with allowedRoutes.namespaces.from=All, so
any chart in any namespace can attach an HTTPRoute whose parentRefs
points at agentgateway-system/agentgateway-proxy — that's the
integration hook for wiring your local-dev workloads in later.
sandboxctl creds prints a copy-paste HTTPRoute template under the
agentgateway block.
Each opt-in tool's master key / API secret is generated per-install and
persisted under ~/.sandboxctl/ (printed by sandboxctl creds). The
graphical Tyk Dashboard is a licensed component and is not part of
Tyk OSS — the gateway here is driven by its control API + API-definition
files.
sandboxctl restart re-asserts whatever you previously opted into (it
detects components by namespace presence), so it never silently
uninstalls something. Removing an add-on means sandboxctl down (which
wipes the cluster) — there is no per-component uninstaller today.
The deprecated --no-arctl / --no-cnpg / --no-agentregistry /
--no-nats / --no-agentgateway / --no-portkey / --no-litellm /
--no-mlflow / --no-tyk / --no-ai-gateway flags are still parsed as
no-ops so older scripts keep working — they exist only because these
components were once default-on. Drop them when you next touch the
script.
Run sandboxctl deploy from your product directory — the same dir you'd
run docker build from. To target a different directory without cd-ing
in, pass it positionally (sandboxctl deploy path/to/repo) or via
--repo path/to/repo. The same selectors work for build and
bootstrap. The CLI will:
- Build + push images. Reads
sandboxctl.yamlat the dir root if it exists (multi-image, dependency-ordered); otherwise walks Dockerfiles and pushes one image per Dockerfile tolocalhost:5050/<dir-name>:latest. - Apply secrets. Reads
k8s/secrets.yamland applies it to the target namespace. On first run, copies it fromk8s/secrets.example.yamland prompts you to fill the values. - Push the chart. The chart subtree is pushed to an in-cluster Gitea
repo at
apps/<chart-name>-chart.git. - Create the Argo Application. Argo syncs from that Gitea URL,
waits for
Synced + Healthy. - Wire the URL. Adds an Istio VirtualService and an
/etc/hostsentry routing<chart-name>.sandbox.app:8443to the chart's primary Service. - Print a status table — namespace, sync, health, pod-ready ratio, URL — so you can see what landed.
1. ./k8s/chart/Chart.yaml ← recommended layout
2. Any Chart.yaml within depth 5 of cwd (Helm only — rendered-manifest
dirs like deploy/ or k8s/ are
intentionally not auto-picked)
3. Interactive prompt for a path ← when nothing is found
If steps 1 and 2 turn up nothing, sandboxctl prompts for a chart path (absolute, or relative to the product dir) and continues with whatever you enter. Press Enter on an empty line to abort.
Override with --chart <dir> if your chart lives outside the product
dir and you want to skip the prompt:
sandboxctl deploy --chart ../platform/manifests/my-app/chart
sandboxctl deploy --chart /abs/path/to/chart| Flag | What |
|---|---|
--repo <dir> |
Run against a product repo other than cwd (positional [path] works too). Same flag is accepted by build and bootstrap. |
--chart <dir> |
Skip auto-discovery and use this chart |
--values <file> |
Pick a specific values file in the chart (default: values-sandbox.yaml, then values-local.yaml) |
--name <name> |
Override the Argo App + URL name (default: Chart.yaml's name) |
--env <name> |
Namespace suffix only — URL stays <name>.sandbox.app. Default dev. |
--no-build |
Skip the build step (registry already has the images) |
--purge-old-tags |
Forwarded to the build step (see Image management). --no-purge-old-tags overrides SANDBOX_BUILD_PURGE_OLD_TAGS=1 from the env. |
sandboxctl undeploy --name <chart> reverses everything — removes the
Argo Application, the VirtualService, and the /etc/hosts entry. The
namespace is preserved unless you delete it manually.
your-app/
├── Dockerfile # or sandboxctl.yaml for multi-image builds
├── sandboxctl.yaml # optional — see Multi-image builds
├── k8s/
│ ├── chart/ # your Helm chart (auto-detected)
│ │ ├── Chart.yaml
│ │ ├── values.yaml
│ │ ├── values-sandbox.yaml # picked automatically by sandboxctl
│ │ └── templates/
│ ├── secrets.yaml # gitignored — applied to namespace
│ └── secrets.example.yaml # template — committed
└── ...
With this layout, sandboxctl deploy from the repo root needs no flags.
sandboxctl deploy reads the chart's values.yaml and adapts to whatever
shape it finds, so you rarely need sandboxctl-specific values. Four passes
run automatically — each is opt-in, opt-out-able, and logged so you can
see why a decision was made.
1. Chart-shipped Ingress is auto-disabled. sandboxctl already creates
an Istio VirtualService for <chart>.sandbox.app, so a chart-defined
Ingress would either fight that route or stall waiting for an
IngressClass this cluster doesn't ship. Any *.ingress.enabled: true
or *.ingress.create: true key in values.yaml is flipped to false
via Argo helm parameters at deploy time. Your chart stays portable — the
toggle is only forced off inside sandboxctl, never edited.
2. Multi-image pinning. For each image in your build manifest,
sandboxctl walks the chart's values.yaml for nodes shaped like
{ repository: …, tag: … } and pins the matching one to the freshly
built image. Matching is by name: image ui ↔ group ui.image,
image backend ↔ group backend.image. Single-image charts still get
the legacy image.repository / image.tag pin. Charts with neither
shape get nothing — sandboxctl never guesses.
3. Smarter primary-Service selection. The VirtualService points at
your chart's most-likely-user-facing Service. The picker scores each
Service in the namespace by name match, web ports (80/8080/3000/8081),
name keywords (ui/web/frontend win; backend/api/worker get
a penalty), and falls back to first alphabetical when scores tie.
4. Explicit overrides. When the heuristic guesses wrong, take control:
-
Annotation — add
sandboxctl.io/primary: "true"to the Service you want routed. Wins against everything else. -
sandboxctl.yaml— declare the entrypoint inline:images: - name: backend context: app/backend - name: ui context: app/ui # Pick the Service routed to <chart>.sandbox.app: primary_service: hello-sandbox-ui # Map build-manifest images to values.yaml groups when the names # don't already match by convention: chart_image_map: ui: ui.image backend: backend.image
The deploy transcript logs each decision (auto-disabling chart toggle ingress.enabled=false, pinning ui.image → localhost:5050/ui:latest,
primary Service: svc/hello-sandbox-ui:80 (scored 90 …)) so you can
debug without re-reading the script.
If your repo has multiple Dockerfiles with build-order dependencies, drop
a sandboxctl.yaml at the root:
images:
- name: base
context: docker/base
aliases: [my-base:latest] # extra tags so downstream FROMs resolve
- name: api
context: docker/api
depends_on: [base]
- name: worker
context: docker/worker
depends_on: [base]
- name: app
context: . # repo-root DockerfileEach entry gets pushed to localhost:5050/<name>:latest. Use them in your
chart values:
image:
repository: localhost:5050/app
tag: latest
pullPolicy: Always # so Argo rollouts pick up new pushessandboxctl build runs the same pipeline standalone if you want to push
images without deploying.
sandboxctl build # build + push all Dockerfiles in cwd
sandboxctl build --purge-old-tags # wipe prior tags of each repo before pushing,
# then GC; only the just-pushed tag remains
# (env equivalent: SANDBOX_BUILD_PURGE_OLD_TAGS=1)
sandboxctl images # list everything in the registry
sandboxctl images rm myapp:v1 # delete one tag
sandboxctl images rm myapp # delete every tag of an image
sandboxctl images prune # delete every image, then GC blobs (alias: purge)
sandboxctl images purge # same as prune — for users coming from podman/docker
sandboxctl images gc # garbage-collect blobs only--purge-old-tags is also accepted by sandboxctl deploy and
sandboxctl bootstrap, where it forwards to the build step.
sandboxctl up # bring everything up (1 worker, ~5 GB RAM)
sandboxctl up --workers 2 # 2 workers, more headroom for concurrent pods
sandboxctl up --workers 3 # 3 workers, recommended for 32 GB+ Macs
sandboxctl status # cluster + workload status + URLs
sandboxctl validate # curl each URL and report HTTP codes
sandboxctl creds # admin passwords for Argo CD + Kargo
sandboxctl argocd-ui # print just the Argo CD URL + admin creds
sandboxctl kargo-ui # print just the Kargo URL + admin creds
sandboxctl restart # re-apply installers, keep cluster + state
sandboxctl restart --rebuild # full wipe-and-rebuild (alias: --full)
sandboxctl restart --workers 3 # resize the cluster (implies --rebuild)
sandboxctl down # remove cluster + LaunchAgent + /etc/hosts + CA
sandboxctl purge # down + remove ~/.sandboxctl (prompts)
sandboxctl trust-ca # re-trust the per-install root CA in the System keychain
sandboxctl untrust-ca # remove the root CA from the System keychain
sandboxctl tui # live status dashboard (Bubble Tea)--workers N picks how many kind worker nodes you get alongside the
control-plane. Range is 1–3; anything else fails fast with a clean
message before any cluster work starts.
| Workers | Best for | Approx. RAM | Concurrent sandbox pods (rough) |
|---|---|---|---|
1 (default) |
Single-task experiments, low memory | ~5 GB | 3–4 |
2 |
Day-to-day on a 16 GB Mac | ~8 GB | 6–8 |
3 |
32 GB+ Mac running heavy workloads | ~12 GB | 10–12 |
Why the cap at 3: each kind worker is its own Docker container with its own image cache and kubelet. Beyond 3 the Mac itself starts swapping — the cluster is happy, the host isn't, and the dev loop gets slower not faster. If you really need more, file an issue with your use case.
You can also set the count via env var:
SANDBOX_WORKER_COUNT=2 sandboxctl up
SANDBOX_WORKER_COUNT=3 sandboxctl bootstrap path/to/repoThe flag wins over the env var when both are set. bootstrap accepts
--workers and forwards it to up. restart --workers N rebuilds the
cluster with the new count (kind doesn't support adding nodes to an
existing cluster — recreating the kind cluster is the only path).
Resizing an existing cluster: --workers N is read only at cluster
creation time. Running sandboxctl up --workers 2 against a cluster
that already exists is a no-op for the worker count: up is idempotent
and skips kind create cluster entirely, so the existing 1-worker
topology stays. To resize:
sandboxctl restart --workers 2 --rebuild # destroys + recreates the cluster with the new count
# or
sandboxctl down && sandboxctl up --workers 2kc get nodes is the way to confirm the live count (one
*-control-plane line plus N *-worker* lines).
Lean by default. Every component sandboxctl installs is tuned for a
laptop: Argo CD ships without its dex/applicationset/notifications
subcomponents (3 fewer pods), everything runs a single replica with small
CPU/memory requests (no CPU limits — only memory limits, so nothing
balloons or gets CPU-throttled), and the shared Postgres has capped memory.
The default up (agentgateway + platform) fits comfortably in the default
4 CPU / 6 GB VM — agentgateway adds a control-plane pod and one
data-plane proxy pod (~150 MB total). Turning on the heavier opt-ins —
especially --with-litellm (~700 MB image, ~1-2 GB RAM) or
--with-ai-gateway (all of them) — is when it's worth bumping the podman
VM:
podman machine stop
podman machine set --cpus 6 --memory 10240 # 10 GB; host should have >= 16 GB
podman machine startBuilds and pushes can fail with no space left on device even when the Mac
has hundreds of GiB free, because containers use a separate VM disk and the
in-cluster registry uses a PVC inside that VM. sandboxctl prune walks the
four surfaces, explains each one, and prompts before doing anything:
sandboxctl prune # interactive — diagnoses all four
sandboxctl prune runtime # only podman/docker VM (positional shorthand)
sandboxctl prune --only registry # explicit form: host | dmgs | runtime | registry
sandboxctl prune --yes # accept every prompt (scripted use; alias: -y)
sandboxctl cleanup # alias for `prune` — same flagsStages, in order:
- macOS host disk — read-only diagnosis; never auto-cleaned.
- Mounted DMGs under
/Volumes— offershdiutil detach. DMGs always show as 100% full indf; that's expected, not a leak. - Container runtime VM (podman or docker) — runs
<runtime> system pruneto free dangling images, stopped containers, and build cache. A second prompt offers to also remove unused tagged images. - In-cluster registry — runs registry GC (safe, blob-only) and, on a separate prompt, a full prune of every tag.
The first up prompts for sudo once — it edits /etc/hosts and trusts
the local root CA in the System keychain. Subsequent runs are silent.
- macOS (Apple Silicon or Intel)
- Homebrew with
kind,kubectl,helm,go, and one ofpodman(recommended) ordocker - ≈6 GiB free RAM for the kind node + cluster workloads
sandboxctl setup-podman installs and configures podman for you (rootful,
6 GiB memory, 60 GiB disk) if it isn't ready yet. To pick a different
size on first run:
sandboxctl setup-podman --disk-size 80 --memory 8192 --cpus 6Existing machine too small? podman can't grow a VM's disk in place —
the only way to bump --disk-size is to recreate the machine, which
wipes its images, containers, and any kind cluster living inside:
sandboxctl down # tear the cluster down first
sandboxctl setup-podman --disk-size 80 --recreate
sandboxctl up # rebuild the clustersandboxctl --with-kagent installs only the kagent controller + UI — no
model provider is configured. Wire a provider/model yourself afterwards
(via the kagent UI or a ModelConfig CRD); sandboxctl will not pull
models or stand up Ollama for you.
Browser
│
▼ https://*.sandbox.app:8443
/etc/hosts (127.0.0.1)
│
▼
LaunchAgent: kubectl port-forward
│
▼ Mac:8443 → istio-ingress:8443
┌──────────────── kind cluster ────────────────┐
│ Istio gateway (wildcard TLS via cert-manager)│
│ │ │
│ ├── argocd-server (argocd ns) │
│ ├── kargo-api (kargo ns) │
│ ├── kagent-ui (kagent ns) │
│ ├── demo-app (demo-app ns) │
│ ├── gitea (gitea ns) │
│ ├── nats + JetStream (nats ns) │
│ ├── agentgateway (agentgateway-system) │ AI Agentic Gateway (default)
│ ├── portkey-gateway (portkey ns) │ ┐
│ ├── litellm (litellm ns) │ │ opt-in legacy alternatives:
│ ├── mlflow (mlflow ns) │ │ --with-portkey / --with-litellm
│ ├── tyk + redis (tyk ns) │ ┘ / --with-mlflow / --with-tyk
│ ├── registry:30050 (sandboxctl-registry ns)
│ └── your apps (one ns each) │
│ │
│ ▲ Argo CD reads charts from in-cluster Gitea │
│ ▲ Pods pull images from the in-cluster registry
└───────────────────────────────────────────────┘
The pieces:
- kind — single-node Kubernetes in a container, run via podman or docker
- cert-manager — bootstrap CA → wildcard cert for
*.sandbox.app - Istio ambient —
istio-base+istiod+istio-cni+ztunnel+istio/gateway. The gateway terminates TLS with the wildcard cert. - Argo CD, Kargo, kagent, demo-app — Helm-installed control plane
- reflector — mirrors annotated Secrets/ConfigMaps across namespaces (inert until a workload chart annotates something)
- In-cluster Gitea — backs the GitOps loop for
sandboxctl deploy - NATS + JetStream — single-replica messaging server with persistent
file store (2 GiB PVC). Reachable from the Mac at
nats://nats.sandbox.app:4222(TCP+TLS, served via an Istio TLS-passthrough listener and a second LaunchAgent port-forward) andwss://nats.sandbox.app:8443for browser/JS clients. Cert is signed by the same per-install CA as everything else. - AI Agentic Gateway — agentgateway default-on (Gateway-API-native, MCP/A2A/LLM);
Portkey, LiteLLM, MLflow, Tyk opt-in, each in its own namespace with an
Istio route + trusted HTTPS URL (see the section above). agentgateway brings
in the upstream Kubernetes Gateway API CRDs; LiteLLM reuses the shared
CloudNativePG cluster (a
litellmdb on agentregistry's Postgres — no second pod; bundled-standalone fallback); Tyk ships a bundled single-replica Redis. - In-cluster registry —
localhost:5050push target, mirrored into the kind node's containerd viahosts.toml - macOS LaunchAgent —
kubectl port-forwardso the gateway is reachable on127.0.0.1:8443across reboots - dnsmasq +
/etc/resolver/sandbox.app— wildcard*.sandbox.app → 127.0.0.1so any product chart can add a VirtualService on a new subdomain (e.g.mcp.fiber.sandbox.app) without touching/etc/hosts
Defaults work for most people. Override via env vars:
| Variable | Default | What |
|---|---|---|
SANDBOX_DOMAIN |
sandbox.app |
DNS suffix for app hostnames |
SANDBOX_CLUSTER_NAME |
sandboxctl |
kind cluster name |
SANDBOX_HTTP_PORT |
8080 |
host HTTP port |
SANDBOX_HTTPS_PORT |
8443 |
host HTTPS port |
SANDBOX_REGISTRY_PORT |
5050 |
host port for the in-cluster registry |
SANDBOX_RUNTIME |
podman |
podman or docker |
PODMAN_MACHINE_CPUS |
4 |
CPUs the podman VM gets at init |
PODMAN_MACHINE_MEMORY_MIB |
6144 |
RAM in MiB |
PODMAN_MACHINE_DISK_GIB |
60 |
virtual-disk size for the podman VM at init. podman has no in-place disk grow — use setup-podman --disk-size N --recreate to bump an existing machine. Same as setup-podman --disk-size N. |
KIND_NODE_IMAGE |
kindest/node:v1.35.0 |
kind node image |
SANDBOX_WORKER_COUNT |
1 |
kind worker nodes (1–3). Same as --workers N. |
SANDBOX_BUILD_PURGE_OLD_TAGS |
0 |
wipe prior tags of each repo before pushing during build/deploy/bootstrap, then registry-GC at the end. Same as --purge-old-tags. |
ARGO_HEALTH_ATTEMPTS |
3 |
number of 180s windows deploy waits for an Argo Application to become Synced+Healthy (default covers up to 9 min for slow CRD-heavy syncs). |
ARGOCD_CHART_VERSION |
9.5.13 |
argo-cd chart version |
KARGO_CHART_VERSION |
1.1.1 |
kargo chart version |
REFLECTOR_CHART_VERSION |
9.1.7 |
emberstack/reflector chart version |
CERT_MANAGER_CHART_VERSION |
v1.16.2 |
cert-manager chart version |
ISTIO_CHART_VERSION |
1.29.2 |
Istio chart version |
KAGENT_CHART_VERSION |
0.9.4 |
kagent chart version |
GITEA_CHART_VERSION |
12.5.0 |
Gitea chart version |
ARCTL_VERSION |
latest |
arctl release to install (latest or a tag like v0.3.3) |
INSTALL_ARCTL |
0 |
install arctl during up (opt-in; --with-arctl or INSTALL_ARCTL=1) |
SANDBOX_KEEP_ARCTL |
0 |
keep arctl on down/purge when set to 1 |
ARCTL_INSTALL_DIR |
/usr/local/bin |
where the arctl binary is installed |
INSTALL_CNPG |
0 |
CloudNativePG operator (opt-in; --with-cnpg; implied by --with-agentregistry) |
INSTALL_AGENTREGISTRY |
0 |
agentregistry server + CNPG cluster (opt-in; --with-agentregistry) |
INSTALL_NATS |
0 |
NATS + JetStream (opt-in; --with-nats) |
INSTALL_KAGENT |
0 |
kagent controller + UI (opt-in; --with-kagent) |
INSTALL_AGENTGATEWAY |
0 |
agentgateway proxy (opt-in; --with-agentgateway) |
INSTALL_PORTKEY |
0 |
Portkey gateway (opt-in; --with-portkey) |
INSTALL_LITELLM |
0 |
LiteLLM proxy (opt-in; --with-litellm) |
INSTALL_MLFLOW |
0 |
MLflow (opt-in; --with-mlflow) |
INSTALL_TYK |
0 |
Tyk OSS gateway (opt-in; --with-tyk) |
AGENTGATEWAY_CHART_VERSION |
v1.2.0 |
pin the agentgateway OCI chart version |
GATEWAY_API_VERSION |
v1.5.0 |
pin the upstream Kubernetes Gateway API CRDs version agentgateway installs |
LITELLM_CHART_VERSION |
latest |
pin the litellm-helm OCI chart version |
LITELLM_DB_MODE |
auto |
LiteLLM Postgres: auto/shared (reuse the shared CNPG cluster) or standalone (chart's own) |
LITELLM_IMAGE_TAG |
main-latest |
LiteLLM image tag (the chart's version-derived default is often unpublished) |
MLFLOW_CHART_VERSION |
latest |
pin the community-charts/mlflow chart version |
TYK_CHART_VERSION |
latest |
pin the tyk-helm/tyk-oss chart version |
PORTKEY_IMAGE |
portkeyai/gateway:latest |
Portkey gateway container image |
Every install gets its own random secrets — there are no shared
passwords baked into this repo. On first up sandboxctl generates a
random Kargo admin password, JWT signing key, and Gitea admin password,
saved to ~/.sandboxctl/ (chmod 600) and reused on subsequent runs.
sandboxctl creds prints them.
The local CA private key is generated by cert-manager inside the cluster and never leaves it.
sandboxctl status # is everything running?
sandboxctl validate # do the URLs answer?
tail -f ~/.sandboxctl/portfwd.log # port-forward outputCommon issues:
- Browser cert warning. Keychain trust didn't take.
sandboxctl trust-care-applies it. ERR_NAME_NOT_RESOLVED./etc/hostsentry missing.sandboxctl deployadds them automatically (you'll be prompted for sudo once).ERR_CONNECTION_REFUSED. LaunchAgent isn't bound to:8443.sandboxctl statuswill say so.sandboxctl restartreinstalls it.ImagePullBackOffafter deploy. Runsandboxctl images— if the tag isn't there, runsandboxctl buildfrom the product dir.
sandboxctl purge # cluster + everything sandboxctl wrote
brew uninstall sandboxctlThe kindest/node image cache and the podman machine itself are left
alone — you may want them for other projects.
MIT — see LICENSE.