Skip to main content

Kubernetes Deployment

Deploy DBBat on Kubernetes with a Deployment, Service, and Ingress.

A Helm chart is also maintained in-tree under charts/ — the manifests below show what the chart produces, in case you want to apply them directly.

Prerequisites

  • Kubernetes cluster (1.19+)
  • kubectl configured
  • PostgreSQL database accessible from the cluster
  • Ingress controller installed (e.g., nginx-ingress, traefik) — or a Gateway API implementation if you prefer an HTTPRoute

Listeners

DBBat exposes five listeners by default. Each can be disabled by setting the matching environment variable to an empty string.

ListenerDefault portEnv var
PostgreSQL proxy5434DBB_LISTEN_PG
Oracle proxy1522DBB_LISTEN_ORA
MySQL/MariaDB proxy3307DBB_LISTEN_MYSQL
MongoDB proxy27018DBB_LISTEN_MONGO
REST API + web UI4200DBB_LISTEN_API

The wire protocols (PostgreSQL, Oracle TNS, MySQL, MongoDB) generally cannot share an HTTP Ingress — see "Exposing the proxy listeners" below for the options.

:::note SSH-tunnelled upstreams These are inbound listeners. If any target server is configured with a via_uid (reached through an SSH bastion), the DBBat pod also needs egress on port 22 to that bastion — allow it in your NetworkPolicy and any egress firewall. :::

Encryption Key Management

DBBat requires a 32-byte AES-256 encryption key to encrypt database credentials at rest. Proper key management is critical for security.

Generating the Key

Generate a cryptographically secure 32-byte key:

openssl rand -base64 32

This produces a base64-encoded string like: K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=

Creating a Kubernetes Secret

Store the encryption key as a Kubernetes Secret:

# From a generated key
kubectl create secret generic dbbat-key \
--from-literal=encryption-key='YOUR_BASE64_KEY_HERE'

# Or from a file
openssl rand 32 > dbbat.key
kubectl create secret generic dbbat-key \
--from-file=encryption-key=dbbat.key
rm dbbat.key # Remove local copy

For production, use a declarative approach with sealed-secrets, SOPS, or your secrets management solution:

# secret.yaml (encrypt this file before committing!)
apiVersion: v1
kind: Secret
metadata:
name: dbbat-key
namespace: dbbat
type: Opaque
stringData:
encryption-key: "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols="

Key Security Best Practices

  1. Never commit plaintext keys - Use sealed-secrets, SOPS, Vault, or external secrets operators
  2. Rotate keys periodically - Plan for key rotation (requires re-encrypting stored credentials)
  3. Limit access - Use RBAC to restrict who can read the secret
  4. Use namespaces - Deploy DBBat in its own namespace with restricted access
  5. Enable encryption at rest - Ensure your cluster encrypts etcd data
# RBAC to restrict secret access
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dbbat-secret-reader
namespace: dbbat
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["dbbat-key"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dbbat-secret-reader-binding
namespace: dbbat
subjects:
- kind: ServiceAccount
name: dbbat
namespace: dbbat
roleRef:
kind: Role
name: dbbat-secret-reader
apiGroup: rbac.authorization.k8s.io

Namespace

Create a dedicated namespace:

kubectl create namespace dbbat

Database Secret

Store the PostgreSQL connection string:

kubectl create secret generic dbbat-db \
--namespace dbbat \
--from-literal=dsn='postgres://user:password@postgres-host:5432/dbbat?sslmode=require'

Deployment

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: dbbat
namespace: dbbat
labels:
app: dbbat
spec:
replicas: 1 # Single replica recommended for proxy consistency
selector:
matchLabels:
app: dbbat
template:
metadata:
labels:
app: dbbat
spec:
serviceAccountName: dbbat
containers:
- name: dbbat
image: ghcr.io/fclairamb/dbbat:latest
ports:
- name: postgres
containerPort: 5434
protocol: TCP
- name: oracle
containerPort: 1522
protocol: TCP
- name: mysql
containerPort: 3307
protocol: TCP
- name: mongo
containerPort: 27018
protocol: TCP
- name: api
containerPort: 4200
protocol: TCP
env:
- name: DBB_DSN
valueFrom:
secretKeyRef:
name: dbbat-db
key: dsn
- name: DBB_KEY
valueFrom:
secretKeyRef:
name: dbbat-key
key: encryption-key
- name: DBB_LISTEN_PG
value: ":5434"
- name: DBB_LISTEN_ORA
value: ":1522"
- name: DBB_LISTEN_MYSQL
value: ":3307"
- name: DBB_LISTEN_MONGO
value: ":27018"
- name: DBB_LISTEN_API
value: ":4200"
resources:
requests:
memory: "32Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /api/v1/health
port: api
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/v1/health
port: api
initialDelaySeconds: 5
periodSeconds: 10
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: dbbat
namespace: dbbat

Service

Expose DBBat within the cluster:

# service.yaml
apiVersion: v1
kind: Service
metadata:
name: dbbat
namespace: dbbat
labels:
app: dbbat
spec:
selector:
app: dbbat
ports:
- name: postgres
port: 5434
targetPort: postgres
protocol: TCP
- name: oracle
port: 1522
targetPort: oracle
protocol: TCP
- name: mysql
port: 3307
targetPort: mysql
protocol: TCP
- name: mongo
port: 27018
targetPort: mongo
protocol: TCP
- name: api
port: 4200
targetPort: api
protocol: TCP
type: ClusterIP

Ingress

Expose the REST API externally:

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: dbbat
namespace: dbbat
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod" # If using cert-manager
spec:
ingressClassName: nginx
tls:
- hosts:
- dbbat.example.com
secretName: dbbat-tls
rules:
- host: dbbat.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dbbat
port:
name: api

Ingress for Traefik

# ingress-traefik.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: dbbat
namespace: dbbat
annotations:
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
tls:
- hosts:
- dbbat.example.com
secretName: dbbat-tls
rules:
- host: dbbat.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dbbat
port:
name: api

HTTPRoute (Gateway API)

As an alternative to Ingress, the REST API and web UI can be exposed through a Gateway API HTTPRoute attached to an existing Gateway (e.g. Istio). This requires the Gateway API CRDs installed in the cluster and a Gateway that accepts routes from the dbbat namespace. TLS is terminated by the Gateway listener (sectionName), so no certificate configuration is needed on the route side:

# httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: dbbat
namespace: dbbat
annotations:
external-dns.alpha.kubernetes.io/hostname: dbbat.example.com
spec:
hostnames:
- dbbat.example.com
parentRefs:
- name: http-gateway
namespace: istio-gateway
sectionName: https
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: dbbat
port: 4200
kind: Service

With the Helm Chart

The in-tree Helm chart renders the same route from the httpRoute.* values (disable the Ingress when using it):

ingress:
enabled: false

httpRoute:
enabled: true
hostnames:
- dbbat.example.com
parentRefs:
- name: http-gateway
namespace: istio-gateway
sectionName: https
paths:
- /
annotations:
external-dns.alpha.kubernetes.io/hostname: dbbat.example.com

:::note Slack interactivity behind a gated gateway Shared gateways are often fronted by a load balancer whose security group allowlists inbound source IPs (a common webhook-hardening pattern). Slack's Approve/Deny button clicks originate from Slack's servers, whose IP ranges are wide and changing — allowlisting them is impractical, so clicks time out even though the endpoint works from your own network. On such deployments, configure Socket Mode (DBB_SLACK_NOTIFY_APP_TOKEN): clicks then arrive over an outbound WebSocket and no gateway change is needed. :::

Exposing the Proxy Listeners

The proxy listeners (PostgreSQL 5434, Oracle 1522, MySQL/MariaDB 3307, MongoDB 27018) cannot be exposed via a standard HTTP Ingress — they speak TCP/wire protocols, not HTTP. Options:

:::note Egress for SSH tunnels Exposing the listeners only covers traffic into the pod. Upstreams configured with a via_uid are dialled through an SSH bastion, which requires egress on port 22 from the DBBat pod to the bastion host. Connections to a bastion are pooled and reused across sessions, so this is a small number of long-lived outbound connections rather than one per client. :::

Option 1: LoadBalancer Service

# service-lb.yaml
apiVersion: v1
kind: Service
metadata:
name: dbbat-proxies
namespace: dbbat
labels:
app: dbbat
spec:
selector:
app: dbbat
ports:
- name: postgres
port: 5434
targetPort: postgres
- name: oracle
port: 1522
targetPort: oracle
- name: mysql
port: 3307
targetPort: mysql
- name: mongo
port: 27018
targetPort: mongo
type: LoadBalancer

Option 2: NodePort Service

# service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: dbbat-proxies
namespace: dbbat
spec:
selector:
app: dbbat
ports:
- name: postgres
port: 5434
targetPort: postgres
nodePort: 30434
- name: oracle
port: 1522
targetPort: oracle
nodePort: 31522
- name: mysql
port: 3307
targetPort: mysql
nodePort: 33307
- name: mongo
port: 27018
targetPort: mongo
nodePort: 32018
type: NodePort

Option 3: TCP Ingress (nginx-ingress)

Configure TCP passthrough in your nginx-ingress controller's ConfigMap:

# tcp-services ConfigMap for nginx-ingress
apiVersion: v1
kind: ConfigMap
metadata:
name: tcp-services
namespace: ingress-nginx
data:
"5434": "dbbat/dbbat:5434"
"1522": "dbbat/dbbat:1522"
"3307": "dbbat/dbbat:3307"
"27018": "dbbat/dbbat:27018"

Complete Deployment

Apply all manifests:

kubectl apply -f namespace.yaml
kubectl apply -f secret.yaml # Or use your secrets management
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml # Or httproute.yaml

Verify the deployment:

kubectl get pods -n dbbat
kubectl get svc -n dbbat
kubectl get ingress -n dbbat # Or: kubectl get httproute -n dbbat

# Check logs
kubectl logs -n dbbat -l app=dbbat

# Test health endpoint
kubectl port-forward -n dbbat svc/dbbat 4200:4200
curl http://localhost:4200/api/v1/health

High Availability Considerations

For production deployments:

  1. Database: Use a managed PostgreSQL service (RDS, Cloud SQL) or a PostgreSQL operator
  2. Replicas: While you can run multiple replicas, consider connection routing implications
  3. Persistence: DBBat is stateless; all state is in PostgreSQL
  4. Monitoring: Add Prometheus annotations for metrics scraping
# Add to deployment.yaml pod template
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "4200"
prometheus.io/path: "/api/v1/health"

External Secrets Operator

For production, consider using External Secrets Operator to sync secrets from Vault, AWS Secrets Manager, etc.:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: dbbat-key
namespace: dbbat
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: dbbat-key
data:
- secretKey: encryption-key
remoteRef:
key: secret/dbbat
property: encryption-key

Next Steps