cd /news/ai-agents/deploying-an-open-source-ai-agent-pl… · home › topics › ai-agents › article
[ARTICLE · art-145420] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Deploying an open-source AI agent platform to Kubernetes: the honest one-command version

A developer published an honest, trap-inclusive guide to deploying ApowerB, an open-source AI agent platform, on Kubernetes using its official cosign-signed Helm chart (apowerb-chart, v0.4.23). The writeup details the prerequisites most "one-command" tutorials omit — storage classes, ingress controllers, cert-manager and DNS — and warns that omitting the secrets values file on any helm upgrade rotates credentials out from under a running database.

by read5 min views1 publishedOct 5, 2026

Every "deploy X to Kubernetes in one command" article is technically true and practically a lie. The helm install really is one line. What the post skips is the empty cluster with no storage class, no ingress controller, no cert-manager, and no DNS. That 90% is what stands between you and a working URL. So here's the honest version for ApowerB, the open-source agent platform: the chart genuinely installs in one command, and here is everything around it, traps included.

ApowerB ships an official Helm chart (apowerb-chart, OCI, cosign-signed) that stands up the whole stack, not just the app:

That's a platform, not a container. Which is exactly why the cluster prerequisites below matter more than for a toy app.

If you already have a cluster with a working storage class, two commands get you running.

1. Generate the secrets. The chart refuses to start without them, on purpose:

umask 077
cat > values-secrets.yaml <<YAML
backend:
  env:
    encryptKey: "$(openssl rand -base64 32)"
th2etl:
  apiKey: "$(openssl rand -hex 32)"
th2pulse:
  ingestToken: "$(openssl rand -hex 32)"
  queryToken: "$(openssl rand -hex 32)"
postgres:
  password: "$(openssl rand -hex 16)"
YAML
chmod 600 values-secrets.yaml

2. Install:

helm upgrade --install apowerb \
  oci://registry-1.docker.io/apowerb/apowerb-chart --version 0.4.23 \
  --namespace apowerb --create-namespace \
  --values values-secrets.yaml --timeout 10m

Then port-forward and open the UI:

kubectl -n apowerb port-forward svc/apowerb-frontend 13000:3000 &

Add a model key in the UI and you have a working agent platform on your cluster. That's the "one command" part, and it's real.

The one rule that bites everyone: pass --values values-secrets.yaml on every helm upgrade. Drop it and you rotate your own credentials out from under a running database. Keep that file safe and version it in your secret store, not in Git.

A real cluster rarely has everything wired. Here's the full walk, which is where most tutorials go quiet.

Kubernetes 1.28+. Three nodes at 4 vCPU / 8 GB give comfortable headroom. Check the ground first:

kubectl get nodes          # all Ready
kubectl get storageclass   # must not be empty
kubectl get ingressclass   # must exist

Lean managed clusters often ship with no default storage class. If get storageclass is empty, install one:

curl -sL -o local-path-storage.yaml \
  https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.37/deploy/local-path-storage.yaml
kubectl apply -f local-path-storage.yaml
kubectl -n local-path-storage rollout status deploy/local-path-provisioner --timeout=120s

kubectl patch storageclass local-path \
  -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

Trap: most storage classes are WaitForFirstConsumer, so a lone PVC stays Pending and looks broken when it's fine. Test with a PVC and a pod together:

kubectl apply -f - <<'YAML'
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: probe-pvc}
spec:
  accessModes: ["ReadWriteOnce"]
  resources: {requests: {storage: 1Gi}}
---
apiVersion: v1
kind: Pod
metadata: {name: probe-pod}
spec:
  restartPolicy: Never
  containers:
    - name: writer
      image: busybox:1.37
      command: ["sh","-c","echo ok > /data/marker && cat /data/marker && sleep 20"]
      volumeMounts: [{name: data, mountPath: /data}]
  volumes:
    - {name: data, persistentVolumeClaim: {claimName: probe-pvc}}
YAML
sleep 25 && kubectl get pvc probe-pvc && kubectl logs probe-pod
kubectl delete pvc probe-pvc pod probe-pod

Want Bound and ok in the logs. If this fails, stop. Nothing else will work.

kubectl get ingressclass
kubectl get svc -A | grep LoadBalancer

Note the class name (Traefik and ingress-nginx are the usual managed add-ons) and the EXTERNAL-IP for DNS.

Let's Encrypt rate-limits at 5 failures/hour, so always start on staging:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: you@example.com
    privateKeySecretRef:
      name: letsencrypt-staging-account-key
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik   # your actual class from Step 2

Trap that costs an afternoon: use ingressClassName, not the deprecated class annotation. Otherwise the ACME challenge Ingress gets no recognised class and the certificate sits at Ready: False forever.

Same install command, now with public access turned on:

helm upgrade --install apowerb \
  oci://registry-1.docker.io/apowerb/apowerb-chart --version 0.4.23 \
  --namespace apowerb --values values-secrets.yaml \
  --set ingress.enabled=true \
  --set ingress.className=traefik \
  --set ingress.host=apowerb.example.com \
  --set ingress.tlsEnabled=true \
  --set ingress.annotations."cert-manager\.io/cluster-issuer"=letsencrypt-staging

Create the first admin while you're at it:

  --set superadmin.email=admin@example.com \
  --set superadmin.password='<a strong one>'

Point an A record at the EXTERNAL-IP, then:

kubectl -n apowerb get certificate
kubectl -n apowerb describe certificate apowerb-tls

Stuck at Ready: False? It's almost always one of: DNS not resolving to the load balancer, port 80 closed (HTTP-01 needs it), the class vs ingressClassName trap, or a staging rate limit. Once it's True, swap the issuer to production ( https://acme-v02.api.letsencrypt.org/directory) and reapply.

The same values mechanism covers the rest:

defaultLlm.model and defaultLlm.apiKey for agents without their own key (LiteLLM under the hood, so OpenAI / Anthropic / Mistral / Gemini / local all work)GOOGLE_INTEGRATION_* (Drive, Gmail, Calendar), MICROSOFT_INTEGRATION_* (Outlook, webhooks, mail), SMTP_* storage.mode and S3_* to push bi_store, uploads, artifacts_store and agents_pool to object storage instead of the PVC The install itself is genuinely one command against an OCI chart, and the chart is honest: it won't boot without real secrets, and it bundles the orchestration, log store and telemetry rather than leaving them as "exercises for the reader." The work that remains isn't ApowerB's, it's the cluster's: storage, ingress, cert-manager, DNS. And that's true of anything serious you deploy.

If you want to see the easy version first, the Docker Compose stack comes up locally in a few commands. But if you're putting agents in front of real users, the Helm chart is the path, and now you've seen all of it, traps included.

Deployed it on something interesting? Drop your cluster setup in the comments.

── more in #ai-agents 4 stories · sorted by recency
── more on @apowerb 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/deploying-an-open-so…] indexed:0 read:5min 2026-10-05 · —