Greentic · Digital Worker OS

The K8s demo runbook, line by line

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.

Note on the bot token

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.

Two JSON files k8s.env.json deployer-answers.json your declared intent Environment store local source of truth /tmp/.../environments 1 · apply kind cluster · namespace gtc-local Worker pod (greentic-start) pulls the bundle, serves the revision port 8080 public listener webhooks only port 8081 loopback only webchat /chat 2 · reconcile ghcr.io (OCI) runtime image + bundle pull Telegram cloud tunnel Your browser localhost:8081 port-forward
Two files describe the worker → apply writes them to the local store → reconcile renders them onto the cluster → the pod pulls its bundle from ghcr, talks to Telegram over a tunnel, and serves the webchat console to you over a port-forward.
Jump to a section
  1. Config — name the paths
  2. Create the cluster (one-time)
  3. Refresh the runtime image
  4. Author the environment (apply)
  5. Push it to the cluster (reconcile)
  6. Wait & open it
  7. Clean up
  8. Glossary

0Config — give the long paths short names

Plain shell variables so the later commands stay short. Nothing is deployed here.

# set two variables

set -gx STORE /tmp/gtc-k8s-demo/.greentic/environments
set -gx HERE  /home/vampik/greenticai/my_demos/k8s-deploy-demo/declarative
Does

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.

Why

So every command below can write $STORE / $HERE instead of repeating long paths — fewer typos, easier to read.

1Create the cluster — one time only

Skip this if kind get clusters already lists gtc-demo.

# spin up a local Kubernetes cluster

kind create cluster --name gtc-demo
kind export kubeconfig --name gtc-demo
Does

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).

Why

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.

2Refresh the runtime image

The worker pod runs a published container image. This makes sure the cluster runs the newest one.

# pull the latest worker image, then load it into the cluster

docker pull ghcr.io/greenticai/greentic-start-distroless:develop
kind load docker-image ghcr.io/greenticai/greentic-start-distroless:develop --name gtc-demo
Does

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.

Why

: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.

3Author the environment — apply

The one command that needs your bot token. It writes your intent into the local store; it does not touch the cluster yet.

# read the manifest, save the whole environment to the store

env TELEGRAM_DEMO_BOT_TOKEN=<your-bot-token> \
    gtc-dev op --store-root $STORE --answers $HERE/k8s.env.json env apply --yes
Does

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.

Why

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.

Key idea: apply vs reconcile

apply 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.

4Push it to the cluster — reconcile

Render the saved environment into real Kubernetes objects and apply them.

# make the live cluster match the stored environment

gtc-dev op --store-root $STORE env reconcile local
Does

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.

Why

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.

5Wait for the worker, then open the chat

Block until the pod is healthy, then forward a local port to its webchat console.

# find the worker, wait for it, forward its console port

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)
Does

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.

Why

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.

Why two ports? (8080 vs 8081)

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.

6Clean up

Stop the forward, tear down the cluster, and tidy Telegram's side.

# stop the port-forward

pkill -f 'port-forward.*gtc-worker'
Does

Kills the background port-forward process by matching its command line (the alternative is just Ctrl+C in its terminal).

Why

The forward runs until you stop it; this frees port 8081 and ends the laptop→pod connection.

# delete the whole cluster and its image cache

kind delete cluster --name gtc-demo
rm -rf /tmp/gtc-k8s-demo
Does

Removes the entire kind cluster (containers, node, cached images) and deletes the local store directory.

Why

Full teardown — nothing keeps running or consuming disk/CPU after you're done.

# tell Telegram to forget the dead webhook

curl -s "https://api.telegram.org/bot<your-bot-token>/deleteWebhook"
Does

Asks Telegram to drop the webhook URL it had on file for your bot.

Why

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.

# OR: clean slate without deleting the cluster

kubectl delete ns gtc-local --ignore-not-found --wait
rm -rf $STORE; mkdir -p $STORE
Does

Deletes just the gtc-local namespace (everything the demo created) and resets the store folder — keeping the kind cluster itself alive.

Why

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.

7Glossary

TermIn plain words
kindKubernetes running inside Docker on your laptop — a free, disposable local cluster.
kubectlThe command-line tool for talking to a Kubernetes cluster.
storeA 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.
packA plug-in capability bound into the environment. Here: a K8s "deployer" pack and a "dev-store" pack that holds secrets.
bundleThe packaged worker app (its chat flow + components). Pulled from an OCI address (ghcr) rather than a local file.
digestA 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 rootThe signing key material the environment trusts, seeded during apply.
reconcileMake a real target (the cluster) match a desired state (the store).
tunnelA temporary public URL (via cloudflared) that lets Telegram's servers reach the worker inside your private cluster.
webhookThe URL Telegram calls whenever your bot gets a message. The worker registers it automatically at boot.
port-forwardA direct laptop→pod connection so you can open a pod's port (8081) in your browser without exposing it publicly.
distrolessA 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 applyenv reconcile. Bot token redacted for public sharing.