Skip to content

Kubernetes Deployment

Template files: deploy/kubernetes/base/

  • Kubernetes cluster with kubectl access
  • A PostgreSQL or MySQL database accessible from the cluster

Deployment is a two-phase process: server first, then agent (the agent needs a token generated by the server).

Terminal window
# Apply namespace, service account, config, PVC, and server deployment
kubectl apply -f deploy/kubernetes/base/namespace.yaml
kubectl apply -f deploy/kubernetes/base/serviceaccounts.yaml
kubectl apply -f deploy/kubernetes/base/configmaps.yaml
kubectl apply -f deploy/kubernetes/base/server-pvc.yaml
kubectl apply -f deploy/kubernetes/base/server-deployment.yaml
kubectl apply -f deploy/kubernetes/base/server-service.yaml
kubectl apply -f deploy/kubernetes/base/networkpolicy.yaml

Wait for the server to become ready:

Terminal window
kubectl -n dbward rollout status deploy/dbward-server
Terminal window
# Get the admin token
kubectl -n dbward exec deploy/dbward-server -- cat /data/admin-token
# Get the agent token
kubectl -n dbward exec deploy/dbward-server -- cat /data/agent-token

Update the Secret with the real values:

Terminal window
kubectl -n dbward create secret generic dbward-secrets \
--from-literal=agent-token=dbw_... \
--from-literal=database-url=postgres://user:pass@db-host:5432/app \
--dry-run=client -o yaml | kubectl apply -f -
Terminal window
kubectl apply -f deploy/kubernetes/base/secrets.yaml # if not created above
kubectl apply -f deploy/kubernetes/base/agent-deployment.yaml

The server Service is ClusterIP (internal only). To access it:

Port-forward (development / first-time setup):

Terminal window
kubectl -n dbward port-forward svc/dbward-server 3000:3000

Production: Create an Ingress or change the Service type to LoadBalancer:

apiVersion: v1
kind: Service
metadata:
name: dbward-server
namespace: dbward
spec:
type: LoadBalancer
ports:
- port: 3000
targetPort: http
selector:
app.kubernetes.io/name: dbward-server

The server uses a PersistentVolumeClaim (dbward-server-data, 1Gi RWO) mounted at /data. This stores the SQLite database, signing keys, and bootstrap tokens. The PVC must survive pod restarts.

The agent is stateless — no PVC needed.

The manifests include a default-deny policy with targeted exceptions:

PolicyAllows
default-denyBlock all ingress/egress by default
allow-dnsUDP/TCP 53 for all pods
server-ingressAgent → Server:3000, Ingress controller → Server:3000
server-egressServer → HTTPS:443 (webhooks, OIDC, S3)
agent-egressAgent → Server:3000, Agent → Database:5432/3306

The base manifests include a single agent Deployment. For multiple databases or environments with different capabilities, duplicate the agent resources:

  1. Copy agent-deployment.yamlagent-staging-deployment.yaml
  2. Create a separate ConfigMap with the staging agent config
  3. Update labels and names to avoid conflicts

Alternatively, use Kustomize overlays or the Helm chart.

Terminal window
# Update image tag in deployment manifests, then:
kubectl -n dbward set image deploy/dbward-server server=ghcr.io/dbward-dev/dbward-server:v0.1.3
kubectl -n dbward set image deploy/dbward-agent agent=ghcr.io/dbward-dev/dbward-agent:v0.1.3

The server applies SQLite migrations automatically on startup.