This runbook stands up a real Greentic digital worker — a webchat console and a Telegram bot — inside a throwaway Kubernetes cluster on your laptop. You describe the whole thing in two JSON files, run two commands to deploy it, and open a browser. Below, every command in plain words: what it does and why it's there.
The original commands embed a live Telegram bot token. Because this page is public, the token has been replaced everywhere with the placeholder <your-bot-token>. Paste your own when you run it.
Plain shell variables so the later commands stay short. Nothing is deployed here.
set -gx STORE /tmp/gtc-k8s-demo/.greentic/environments
set -gx HERE /home/vampik/greenticai/my_demos/k8s-deploy-demo/declarative
set -gx is the fish-shell way of saying "create a global, exported variable" (the same as export in bash). STORE points at the folder that will hold your environment's saved state. HERE points at the folder containing the two JSON files that describe the demo.
So every command below can write $STORE / $HERE instead of repeating long paths — fewer typos, easier to read.
Skip this if kind get clusters already lists gtc-demo.
kind create cluster --name gtc-demo
kind export kubeconfig --name gtc-demo
kind = "Kubernetes IN Docker." It runs a complete, throwaway Kubernetes cluster inside Docker containers on your machine — no cloud account needed. The first line creates the cluster gtc-demo; the second writes its connection details into your kubeconfig so kubectl knows how to reach it (and makes it the active context).
You need somewhere to deploy. kind is your local stand-in for a real cloud Kubernetes cluster, free and disposable. It's "one-time" because the cluster survives between demo runs.
The worker pod runs a published container image. This makes sure the cluster runs the newest one.
docker pull ghcr.io/greenticai/greentic-start-distroless:develop
kind load docker-image ghcr.io/greenticai/greentic-start-distroless:develop --name gtc-demo
First line downloads the latest :develop worker image from GitHub's container registry (ghcr.io) to your local Docker. Second line copies that image from your local Docker into the kind cluster's internal image cache.
:develop is a moving tag — it points at a different build every time someone publishes. Kubernetes, seeing a tag it already has cached, won't bother re-downloading it and would happily run a stale copy. Pulling fresh and side-loading guarantees the cluster runs today's worker code, not last week's.
applyThe one command that needs your bot token. It writes your intent into the local store; it does not touch the cluster yet.
env TELEGRAM_DEMO_BOT_TOKEN=<your-bot-token> \
gtc-dev op --store-root $STORE --answers $HERE/k8s.env.json env apply --yes
env VAR=value cmd runs the command with one extra environment variable set — here, your bot token, passed in memory so it never gets written to a file. gtc-dev op is the Greentic operator CLI. It reads k8s.env.json (the "env-manifest" — your declared intent for the whole environment) and reconciles the store to match it: it creates the environment, seeds its trust root, binds the two packs (the K8s deployer + the dev-store for secrets), pulls the bundle once from its OCI address and verifies its digest, registers the Telegram endpoint, and saves the bot token. --yes skips the confirmation prompt.
This is the single declarative "describe everything" step. Instead of a dozen little setup commands, one manifest captures the desired state and apply makes the store match it. It's also the only place the bot token is needed — every later command is token-free.
apply vs reconcileapply reconciles the store (your local source of truth). reconcile (next step) reconciles the live cluster to match that store. They're deliberately two separate commands — authoring your intent is kept distinct from pushing it to real infrastructure.
reconcileRender the saved environment into real Kubernetes objects and apply them.
gtc-dev op --store-root $STORE env reconcile local
Takes the environment named local from the store and applies it to the running cluster: it creates the namespace, a ConfigMap with the environment config, a Secret holding the bot token, a NetworkPolicy, and the worker Deployment. The worker pod then boots, pulls the bundle from ghcr, re-checks the digest, starts serving the chat flow, opens a public tunnel, and auto-registers the Telegram webhook.
Step 3 only updated your local store. This is the step that actually changes the cluster. Splitting them means you can review/author safely first, then deploy as a distinct action.
Block until the pod is healthy, then forward a local port to its webchat console.
set WORKER (kubectl -n gtc-local get deploy -o name | string match '*worker*')
kubectl -n gtc-local rollout status $WORKER --timeout=240s
kubectl -n gtc-local port-forward $WORKER 8081:8081
# → open http://localhost:8081/chat (webchat — runs alongside the Telegram tunnel)
# → Telegram: just message the bot (the worker registered its webhook at boot)
Line 1 asks Kubernetes for the worker Deployment's name and stores it in WORKER (string match '*worker*' is a fish filter that keeps the line containing "worker"). Line 2 waits — up to 4 minutes — for the worker to finish rolling out and become Ready. Line 3 connects your laptop's localhost:8081 straight to the pod's port 8081.
The webchat console is served only on the worker's loopback admin listener (port 8081), never on the public port. Port-forwarding is a direct laptop→pod tunnel that bypasses the public edge entirely — so you reach the console locally even while the public port is busy serving Telegram. Telegram itself needs nothing more: the worker already told Telegram where to send messages when it booted.
The worker runs two listeners. Port 8080 is public — a cloud tunnel fronts it so Telegram can deliver webhook calls — and it accepts webhooks only; it refuses the chat console and the invoke API. Port 8081 is loopback-only (never exposed to the internet) and serves the webchat console. This split is what lets webchat and Telegram run at the same time without exposing the console to the public.
Stop the forward, tear down the cluster, and tidy Telegram's side.
pkill -f 'port-forward.*gtc-worker'
Kills the background port-forward process by matching its command line (the alternative is just Ctrl+C in its terminal).
The forward runs until you stop it; this frees port 8081 and ends the laptop→pod connection.
kind delete cluster --name gtc-demo
rm -rf /tmp/gtc-k8s-demo
Removes the entire kind cluster (containers, node, cached images) and deletes the local store directory.
Full teardown — nothing keeps running or consuming disk/CPU after you're done.
curl -s "https://api.telegram.org/bot<your-bot-token>/deleteWebhook"
Asks Telegram to drop the webhook URL it had on file for your bot.
That webhook pointed at the temporary tunnel address, which is gone now that the cluster is down. If you don't clear it, Telegram keeps retrying delivery to a dead URL. Pure hygiene — and it leaves the bot clean for next time.
kubectl delete ns gtc-local --ignore-not-found --wait
rm -rf $STORE; mkdir -p $STORE
Deletes just the gtc-local namespace (everything the demo created) and resets the store folder — keeping the kind cluster itself alive.
A lighter reset for re-running the demo from scratch without paying the cost of recreating the whole cluster. --ignore-not-found means "don't error if it's already gone"; --wait means "block until it's fully deleted" so the next run starts truly clean.
| Term | In plain words |
|---|---|
| kind | Kubernetes running inside Docker on your laptop — a free, disposable local cluster. |
| kubectl | The command-line tool for talking to a Kubernetes cluster. |
| store | A folder on disk that holds your environment's saved state — the local "source of truth" that apply writes and reconcile reads. |
| manifest (env-manifest) | The k8s.env.json file: a single declaration of the whole environment — packs, secrets, the bundle, the messaging endpoint. |
| pack | A plug-in capability bound into the environment. Here: a K8s "deployer" pack and a "dev-store" pack that holds secrets. |
| bundle | The packaged worker app (its chat flow + components). Pulled from an OCI address (ghcr) rather than a local file. |
| digest | A sha256 fingerprint of the bundle. Pinning it means the worker only runs the exact bytes you expect — verified once at apply and again at boot. |
| trust root | The signing key material the environment trusts, seeded during apply. |
| reconcile | Make a real target (the cluster) match a desired state (the store). |
| tunnel | A temporary public URL (via cloudflared) that lets Telegram's servers reach the worker inside your private cluster. |
| webhook | The URL Telegram calls whenever your bot gets a message. The worker registers it automatically at boot. |
| port-forward | A direct laptop→pod connection so you can open a pod's port (8081) in your browser without exposing it publicly. |
| distroless | A minimal container image (no shell, no package manager) — smaller and safer; it's the worker's runtime image. |
Greentic K8s declarative demo · webchat + Telegram on a local kind cluster · the full two-command flow is env apply → env reconcile. Bot token redacted for public sharing.