Deploying stacks
aislang is a compile-then-deploy tool: a single .ais source describes your
whole system — models, agents, prompts, MCP servers, evals, budgets — and
aislang apply turns it into a running stack. The deploy block picks the
target. This chapter covers the two self-hosted targets: Docker Compose
(the default) and Kubernetes (Helm).
Docker Compose (default)
With target = compose, aislang apply emits a stack.yml and brings it up
with docker compose up -d. The stack is a small, fixed set of services:
- A LiteLLM router — the central model gateway. Every agent's API calls go
through it, which is where model routing, fallback, and per-model config
live. Published to the host on
localhost:4000. - A Jaeger sidecar — receives OpenTelemetry traces from
chatandeval; UI atlocalhost:16686. - A budget-enforcement counter — accounts per-call spend against the caps
declared in your
budgetblock. - Your agents — the deployed agents declared in the source.
MCP servers are not long-running Compose services. They are spawned as
stdio child processes by aislang chat for the duration of a session, then
torn down when the session ends.
Provider credentials never enter the emitted artifacts. aislang plan writes
only a safe .env.example template (safe to commit) listing the env vars
LiteLLM expects — for example ANTHROPIC_API_KEY for an Anthropic primary or
OLLAMA_API_BASE for an Ollama primary. You copy it to a local .env, fill in
real values, and docker compose loads it at runtime.
deploy "local" {
target = compose
}
# Copy the emitted template and fill in your keys.
cp .env.example .env
# edit .env — add ANTHROPIC_API_KEY, etc.
# Stand up the stack (LiteLLM + Jaeger + budget counter + agents) in Docker.
aislang apply examples/support-agent.ais
# Chat with a deployed agent. `aislang chat` runs the tool-use loop in-CLI,
# talking to the Compose-managed LiteLLM at localhost:4000 and spawning the
# declared MCP servers as stdio children for the session.
aislang chat support examples/support-agent.ais
# When you're done, tear it all down (containers + volumes).
aislang destroy examples/support-agent.ais
Browser chat via Open WebUI
For users without an MCP-aware client, add web_ui = true to your deploy
block's expose { … } to bring up an Open WebUI
sidecar wired to the in-stack LiteLLM:
deploy "local" {
target = compose
expose {
web_ui = true # → http://localhost:3000 after `aislang apply`
}
}
After aislang apply, browse to http://localhost:3000, sign up (local
SQLite — the first user becomes admin), and pick any model alias from the
dropdown. Every model LiteLLM is configured to route appears there — handy for
confirming an agent's chosen model actually replies without writing any client
code.
aislang destroy runs docker compose down -v, which wipes the
openwebui-data volume — chat history and local accounts go away with the rest
of the stack.
Kubernetes (Helm)
Flip target = kubernetes and aislang apply packages the same stack as a
Helm chart and installs it with helm upgrade --install.
deploy "prod" {
target = kubernetes
expose {
web_ui = true # → Open WebUI Service (ClusterIP)
ingress = "support.example.com" # opt-in Ingress; omit for port-forward
}
}
Prerequisites
You need docker, kubectl, and helm. For a local cluster,
kind (Kubernetes in Docker) gives you a working
cluster in about 30 seconds:
# Docker — required prerequisite (same as target = compose).
sudo apt update && sudo apt install -y docker.io
sudo usermod -aG docker $USER && newgrp docker
# kubectl — the Kubernetes CLI.
curl -sSL -o /tmp/kubectl "https://dl.k8s.io/release/$(curl -sSL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -m 0755 /tmp/kubectl /usr/local/bin/kubectl
# helm — chart installer.
curl -sSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# kind — local cluster.
curl -sSL -o /tmp/kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64
sudo install -m 0755 /tmp/kind /usr/local/bin/kind
# Spin up a cluster and sanity-check it.
kind create cluster
kubectl get nodes
On macOS, swap the apt install step for brew install docker kubectl helm kind and make sure Docker Desktop is running.
Provider credentials
aislang never writes secret bodies into the chart values. You create the
provider-creds Secret yourself; the chart references it via
envFrom: secretRef: aislang-providers:
kubectl create secret generic aislang-providers \
--from-literal=ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
--from-literal=OPENAI_API_KEY="$OPENAI_API_KEY" \
--from-literal=OLLAMA_API_BASE="$OLLAMA_API_BASE"
Plan, apply, port-forward, destroy
# Plan — writes manifests/ + a Helm chart/ to disk, but installs nothing.
aislang plan examples/support-agent-k8s.ais
# Apply — runs `helm upgrade --install <deploy-label> chart/`.
aislang apply examples/support-agent-k8s.ais
Open WebUI is a ClusterIP Service by default — reachable only from inside the
cluster. Either port-forward it (works on any cluster, no Ingress controller
needed) or set ingress = "<host>" in your source and point DNS at your
cluster's Ingress controller:
kubectl port-forward svc/openwebui 3000:8080
aislang chat runs from your machine and dials LiteLLM at a fixed
localhost:4000. Under Kubernetes, LiteLLM is a ClusterIP Service, so
port-forward it in a second terminal before chatting:
kubectl port-forward svc/litellm 4000:4000
aislang chat support examples/support-agent-k8s.ais
Tear it down with either command — both are idempotent:
aislang destroy examples/support-agent-k8s.ais # runs `helm uninstall <deploy-label>`
# or directly:
helm uninstall <deploy-label>
Overriding chart values
The chart exposes a small set of install-time overrides you can apply without
re-running aislang plan:
helm upgrade --install prod ./chart \
--set litellm.replicas=3 \
--set openwebui.persistence.size=5Gi \
--set ingress.host=support.example.com