YouBothAgent▾
You — Business rules and flows you own. Read these yourself.
Both — Know the idea; your agent follows the details.
Agent — Conventions and references your agent follows. Look up as needed.
General▾
Interface▾
Observability▾
Performance▾
Native▾
Development▾

Kubernetes

Every Akan app deploys with the same Helm chart, infra/app. It creates four resources in the namespace <appName>-<branch>:
ResourceNameWhat it does
Deploymentapp-deploymentRuns the app image in exactly one pod.
Serviceapp-svcExposes the app on port 8282 inside the cluster.
Ingressapp-ingressConnects your domains to the Service and gets their TLS certificate.
PersistentVolumeClaimsqlite-dataKeeps the sqlite data in /workspace/sqlite across pod restarts.
The namespace picks the branch
You never write the branch as a value. The chart reads it from the release namespace:
  1. You deploy into <appName>-<branch>, for example myapp-main.
  2. The chart splits the name on - and takes the second part, main, as the branch.
  3. It reads the main: block of the values and passes AKAN_PUBLIC_ENV=main to the pod.

Architecture

A request enters through the Ingress, and the Service passes it to the pod, which keeps its data on the PVC. Alongside, the kubelet checks the pod's health.
Request path
Domain<appName>-<branch>.<serveDomain>
IngressTLS, one host per subRoute and domain
Service :8282
Podreplicas: 1
PVC/workspace/sqlite
kubelet
  • Always one pod. replicas: 1 is fixed in the template. To scale, raise AKAN_REPLICA inside the pod instead of adding pods.
  • Why only one. The sqlite PVC is ReadWriteOnce, so it attaches to one node at a time and pods cannot spread across nodes.
  • One certificate for every host. The default host, each subRoute host and each production domain share the TLS secret cert-<appName>-<branch>.

Values

There is no single values.yaml: Helm reads four files in order, and a later file overrides an earlier one. An app's own file usually holds just its name and production domains.
#FileWhat it holds
1_common-values.yamlDefaults for the debug, develop and main branches: replica, resources, storage.
2_common-secret.yamlValues every app shares: repoName, serveDomain, image.registry.
3<appName>-values.yamlThe app's own values: appName, subRoutes, domains, and any override.
4<appName>-secret.yamlThe app's own secret values, and it may be empty.
Both *-secret.yaml files stay out of git. bun run downloadSecret fetches every file listed in infra/master/jenkins/getSecrets.sh, so add a new app's file there.
Deploy command
The Jenkins deploy stage runs these two commands from infra/ for each app:
Terminal
  • The -f order is the priority. A later file overrides every key it repeats.
  • -n picks the branch. myapp-main selects the main: block.
  • rollout restart brings in the new build. The restarted pod pulls the image again, so it runs the build just pushed.
An app's values file
A production app that needs more capacity than the defaults writes something like this:
infra/app/values/myapp-values.yaml
  • Top-level keys apply to every branch. appName and subRoutes sit at the root.
  • A main: block only touches main. Keys you leave out keep the _common-values.yaml default, while a list such as domains is replaced whole.
Top-level keys
A key tagged _common-secret.yaml is set there once for every app.
appNamestring
Names the namespace <appName>-<branch>, the image path and the default host.
repoNamestring_common-secret.yaml
The workspace part of the image path <registry>/<repoName>/<appName>.
serveDomainstring_common-secret.yaml
The base domain, so the default host is <appName>-<branch>.<serveDomain>.
subRoutesstring[]default []
Adds one <subRoute>-<branch>.<serveDomain> host and TLS name per basePath.
image.registrystring_common-secret.yaml
The registry host.
image.tagstringdefault <branch>-live
Write it only to pin one specific build.
Per-branch keys
Written under a branch block such as main:. The defaults come from _common-values.yaml.
<branch>.domainsstring[]default []
Extra hosts and TLS names for that branch, such as a production domain.
<branch>.app.replicastringdefault "0,0,1"
Becomes AKAN_REPLICA in the pod: process counts for federation, batch and all.
<branch>.app.solostringdefault unset
Becomes AKAN_SOLO; write false to keep a gateway in front of a single replica.
<branch>.app.resources.requests{ memory, cpu }default 250M / 0.05 (main: 1G / 1)
The memory and CPU the pod requests.
<branch>.app.resources.limits{ memory, cpu }default 1G / 0.5 (main: 4G / 4)
The pod's limit; the CPU limit also sets how many images the optimizer encodes at once.
<branch>.app.resources.storagestringdefault 2Gi (main: 5Gi)
The size of the ReadWriteOnce PVC mounted at /workspace/sqlite.

Scale

<branch>.app.replica becomes AKAN_REPLICA in the pod: three process counts, one per role. Raise it together with the CPU and memory limits.
federation
Serves requests and skips services and internals pinned to serverMode: "batch".
batch
Never listens, and runs scheduled and queued internals, including those pinned to batch.
all
Serves requests and runs every internal.
Common values
AKAN_REPLICA
Requests
batch internals
Gateway
One process, no gateway
0,0,1
✓
✓
The chart default: one all-purpose process.
1,0,0
✓
One request process, so nothing pinned to batch runs.
Several processes behind a gateway
2,1,0
✓
✓
✓
Two request processes and one batch worker.
✓YesNo
Health probes
With a single process there is no gateway to restart it, so the kubelet does. The chart points three probes at /_akan/app/health, which a solo process answers itself.
ProbePeriodTimeoutFailures
↳ What it does
startupProbe5s1s24
Waits up to 2 minutes for boot, since SSR loads its route artifacts before it listens.
livenessProbe15s3s3
Restarts a stuck server after three misses in a row.
readinessProbe10s3s2
Takes the pod out of the Service after two misses in a row.
  • A 40-second grace period. On shutdown the server drains for up to 30 seconds (AKAN_SHUTDOWN_TIMEOUT_MS). The extra 10 seconds keep SIGKILL from landing the moment that drain ends.
  • A gateway restarts its own children. With several processes, such as 2,1,0, the gateway restarts a crashed one, and the probes still watch the pod.

Open Console

The built image already contains console.js. Open it inside the running pod with kubectl exec:
Terminal
  • Namespace and container. -n is <appName>-<branch>, and the container is always named app.
  • AKAN_CONSOLE=1 goes on this command only. The image runs in production mode on every branch, so the console refuses to open without it.
  • It is a separate process. The console starts its own server process in the pod that does not listen and runs no internal jobs or queue workers. It does not attach to the memory of the running main.js.
  • The running server's logs still reach it. .tail and .trace read them through the control socket in the runtime directory.

Tips

  • Start with small requests. Watch the metrics first, then raise the limits.
  • Grow storage early. Resize the sqlite PVC before it becomes urgent.
  • Write every host out. Explicit subRoutes and domains keep the Ingress rules predictable; keep them in step with routes in akan.config.ts. The chart only opens a host, and the app decides which basePath answers it.
  • After a manual helm upgrade, restart the rollout. The default tag <branch>-live keeps its name for every build, so the Deployment does not change and the pod keeps the old build until kubectl rollout restart.
Related pages

Released under the MIT License

Connect your AI to these docs

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js All rights reserved.System managed bybassman