Skip to main content

Self-hosted DBtune on Kubernetes

Overview

DBtune can be deployed on any Kubernetes cluster using the official Helm chart. This is the recommended deployment method for customers running Kubernetes infrastructure such as AWS EKS, Google GKE, Azure AKS, Red Hat OpenShift, Rancher, or k3s.

If you are running a single VM without Kubernetes, see the Docker Compose self-hosted guide instead.

The Helm chart ships inside the same offline bundle as the Docker Compose deployment — dbtune-self-hosted-<version>.tar — which already contains every image the chart needs. No container registry access is required, which makes this suitable for air-gapped clusters too.

Once DBtune has enabled self-hosting for your organization, the bundle is available to download directly from the platform: open your organization page and download the latest self-hosted release. That single archive is everything you need.

Prerequisites

  • A running Kubernetes cluster (EKS, GKE, AKS, OpenShift, Rancher, k3s, or similar)
  • kubectl configured and connected to your cluster
  • helm v3 or later installed
  • A default StorageClass in your cluster (kubectl get storageclass) — managed clusters (EKS, GKE, AKS, OpenShift) have one already; a bare-metal or on-prem cluster needs one set up first

System Requirements

Requirements scale with the number of connected databases and concurrent tuning sessions.

vCPURAM
Minimal (1–5 databases)412 GB
Recommended (5–10 databases)816 GB
note

These numbers assume the control plane runs separately, as on a managed cluster. A single-node test cluster needs extra vCPU and memory on top of these, since the same node also runs the control plane.

Installation

Step 1 — Unpack the release

Copy the bundle to a machine with access to your cluster, then unpack it and locate the Helm chart:

tar -xf dbtune-self-hosted-<version>.tar
cd dbtune-self-hosted-<version>/helm

Step 2 — Import the bootstrap images

Every image the chart needs ships in the bundle, and the chart runs its own in-cluster image registry to serve them — no external registry, no GitHub account required. Three small images (the registry itself, plus two helper images) can't come from that registry, since it does not exist yet when they are needed, so they need a one-time import on every node in your cluster before you install.

Follow one of the two options below, whichever matches your cluster: containerd for everything except OpenShift (this includes EKS, AKS, GKE, kubeadm, and k3s), or OpenShift.

containerd

Copy the three tarballs from images/node-import/ onto each node, then run:

ctr -n k8s.io images import zot.tar
ctr -n k8s.io images import curl.tar
ctr -n k8s.io images import stable.tar

On k3s, which runs its own bundled containerd separate from any system-wide install, use k3s ctr instead of ctr.

OpenShift

OpenShift nodes run CRI-O rather than containerd. Use podman load instead via a debug pod:

oc debug node/<name> -o yaml -- chroot /host sleep 3600 > /tmp/debug-pod.yaml
oc apply -f /tmp/debug-pod.yaml # note the pod name it creates
oc cp images/node-import/zot.tar default/<pod>:/host/var/tmp/zot.tar
oc cp images/node-import/curl.tar default/<pod>:/host/var/tmp/curl.tar
oc cp images/node-import/stable.tar default/<pod>:/host/var/tmp/stable.tar
oc exec <pod> -- chroot /host podman load -i /var/tmp/zot.tar
oc exec <pod> -- chroot /host podman load -i /var/tmp/curl.tar
oc exec <pod> -- chroot /host podman load -i /var/tmp/stable.tar
oc delete pod <pod> -n default

Step 3 — Allow the in-cluster registry over HTTP

The in-cluster registry serves plain HTTP, since it is only reachable inside the cluster's own network. Both containerd and CRI-O default to HTTPS for any pull, so each node needs to be told this registry is the exception.

Check your cluster's service network range first — the chart's default registry address (10.96.0.100) works on most kubeadm clusters, but not on OpenShift, whose default range is different:

kubectl get network.config/cluster -o jsonpath='{.spec.serviceNetwork}'   # OpenShift

If the default does not fall inside your range, pick a free address in it and pass --set registry.clusterIP=<address> when you install in step 7, using that same address below.

Follow the option that matches your cluster, same as step 2.

containerd

Add to /etc/containerd/config.toml (the exact plugin name depends on your containerd version):

[plugins.'io.containerd.cri.v1.images'.registry]
config_path = '/etc/containerd/certs.d'

Then create /etc/containerd/certs.d/<registry-address>/hosts.toml:

server = "http://<registry-address>"

[host."http://<registry-address>"]
capabilities = ["pull", "resolve", "push"]

Restart containerd after any change to config.toml:

sudo systemctl restart containerd

OpenShift

This is configured cluster-wide instead of per node:

oc patch image.config.openshift.io/cluster --type=merge \
-p '{"spec":{"registrySources":{"insecureRegistries":["<registry-address>"]}}}'

Step 4 — Create your values file

cp dbtune/values.yaml my-values.yaml

Edit my-values.yaml and fill in the required fields:

# URL the platform will be accessed from — must match how users reach the platform
# e.g. https://dbtune.yourdomain.com for public access, or http://internal-host for private/air-gapped
frontendUrl: "https://dbtune.yourdomain.com"

django:
secretKey: "<random-50-char-string>" # openssl rand -base64 40
superuserEmail: "admin@yourdomain.com"
superuserPassword: "<strong-password>"
superuserApiKey: "<uuid4>" # python3 -c "import uuid; print(uuid.uuid4())"

email:
host: "smtp.yourdomain.com"
port: 587
useTls: "true"
from: "noreply@yourdomain.com"
password: "<smtp-password>"

postgres:
password: "<strong-password>"
prefectPassword: "<strong-password>"

influxdb:
token: "<random-string>"
initPassword: "<strong-password>"

Step 5 — Configure access

How users reach the platform depends on your network setup. Choose the option that fits your environment.

On OpenShift, skip this step — the chart creates OpenShift Routes automatically once you set openshift.enabled: true in step 7, and the ingress.* values below have no effect there. Just set frontendUrl correctly in step 4.

Option A — Ingress (public or internal domain)

The chart can create a Kubernetes Ingress resource to route traffic to the platform. Your cluster must have an ingress controller already installed (e.g. nginx, Traefik, ALB).

ingress:
enabled: true
className: "nginx" # or "traefik", "alb", etc.
annotations: {} # add ingress controller specific annotations here

To enable TLS, create a secret from your certificate files first:

kubectl create secret tls dbtune-tls \
--cert=tls.crt \
--key=tls.key \
-n dbtune

Then reference it in your values:

ingress:
tls:
enabled: true
secretName: dbtune-tls

Option B — Port-forward (air-gapped, internal, or testing)

For air-gapped or private deployments where no ingress controller is available, disable ingress and access the platform via port-forward:

ingress:
enabled: false
kubectl port-forward -n dbtune svc/web <local-port>:8000

The right side (8000) is the port the web service listens on inside the cluster and must stay as-is. The left side is the local port you want to access it on — you can use any available port on your machine.

Then set frontendUrl to match, e.g. http://internal-hostname:<local-port>.

Step 6 — Load the image bundle

This also creates the dbtune namespace:

kubectl apply -f image-bundle-pvc.yaml
kubectl -n dbtune wait --for=condition=Ready pod/image-bundle-loader --timeout=60s
kubectl -n dbtune cp ../images/bundle.tar.gz image-bundle-loader:/images/bundle.tar.gz

Step 7 — Deploy

helm install dbtune dbtune \
-f my-values.yaml \
-n dbtune

On OpenShift, first grant the anyuid SCC to the namespace, since OpenShift's restricted SCC blocks containers from running as specific UIDs by default, then deploy with an extra flag:

oc adm policy add-scc-to-group anyuid system:serviceaccounts:dbtune

helm install dbtune dbtune \
-f my-values.yaml \
-n dbtune \
--set openshift.enabled=true

openshift.enabled: true also creates OpenShift Routes instead of standard Ingress resources — no ingress controller needed — and fixes PostgreSQL data directory permissions for OpenShift's restricted SCC.

helm install blocks until the in-cluster registry is seeded with every image (normally under a minute), so a successful install means the platform can already pull everything it needs.

Step 8 — Wait for all pods to be ready

kubectl get pods -n dbtune -w

All 11 pods should reach 1/1 Running:

db              1/1  Running
dbtuner 1/1 Running
indigo 1/1 Running
influxdb 1/1 Running
ingest 1/1 Running
prefect-server 1/1 Running
redis 1/1 Running
registry 1/1 Running
web 1/1 Running
worker 1/1 Running
ws 1/1 Running

Step 9 — Access the platform

Open the URL you set as frontendUrl and log in with the superuser credentials from your values file.


Upgrading

When a new version is released, download the new bundle and repeat steps 1 through 3 with it, so the in-cluster registry has the new images available, then run:

helm upgrade dbtune dbtune \
-f my-values.yaml \
-n dbtune

Keep your existing my-values.yaml — carry over any values the new release adds, but do not regenerate the secrets in step 4, or the upgraded platform loses access to existing data.


Scaling

The platform includes a dedicated ingest service that handles all agent API traffic (/api/v1/agent/*) separately from the user-facing web service. This means agent load does not affect dashboard performance and each can scale independently.

For deployments with a large number of agents (20+), enable autoscaling on the ingest service:

ingest:
autoscaling:
enabled: true
minReplicas: 1
maxReplicas: 5
targetCPUUtilizationPercentage: 60
note

Autoscaling requires the Kubernetes metrics-server to be installed in your cluster. Most managed Kubernetes services (EKS, GKE, AKS) include it by default. For k3s, install it with kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml.


DBtune agent

The DBtune agent connects to the platform via the frontendUrl you configured. Set the agent's server_url to that same value.

The agent is open-source and written in Go: github.com/dbtuneai/dbtune-agent. More information can be found in the agent documentation.


Email / SMTP

Real SMTP is required for password resets and notifications. Example for Gmail:

email:
host: "smtp.gmail.com"
port: 587
useTls: "true"
from: "your@email.com"
password: "<app-password>"

Security

All internal services (postgres, redis, influxdb, prefect, worker, ws, indigo, registry) communicate privately inside the cluster and are not reachable from outside.

The web service is only exposed externally if you enable ingress (ingress.enabled: true) or explicitly expose it via port-forward. With ingress.enabled: false nothing is exposed to the network automatically.