This guide covers deploying the ToolHive Registry API server to Kubernetes.
- Overview
- Prerequisites
- Basic Deployment
- Database Configuration
- Authentication Setup
- Production Considerations
- Complete Examples
The Registry API is designed to run as a sidecar container alongside the ToolHive Operator's MCPRegistry controller, but it can also run standalone.
- Automatic database migrations on startup
- Health and readiness probes
- Graceful shutdown (30-second timeout)
- ConfigMap-based configuration
- Secret-based credential management
- Kubernetes cluster (1.24+)
kubectlconfigured to access your cluster- PostgreSQL database (if using database backend)
- Container image:
ghcr.io/stacklok/toolhive/thv-registry-api:latest
apiVersion: v1
kind: ConfigMap
metadata:
name: registry-api-config
namespace: toolhive
data:
config.yaml: |
registryName: my-registry
registries:
- name: toolhive
format: toolhive
git:
repository: https://github.com/stacklok/toolhive.git
branch: main
path: pkg/registry/data/registry.json
syncPolicy:
interval: "15m"
auth:
mode: anonymous # For development only!
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: registry-api
namespace: toolhive
spec:
replicas: 1
selector:
matchLabels:
app: registry-api
template:
metadata:
labels:
app: registry-api
spec:
containers:
- name: registry-api
image: ghcr.io/stacklok/toolhive/thv-registry-api:latest
args:
- serve
- --config=/etc/registry/config.yaml
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /readiness
port: http
initialDelaySeconds: 5
periodSeconds: 5
volumeMounts:
- name: config
mountPath: /etc/registry
readOnly: true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: config
configMap:
name: registry-api-config
---
apiVersion: v1
kind: Service
metadata:
name: registry-api
namespace: toolhive
spec:
selector:
app: registry-api
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
type: ClusterIPApply the manifests:
kubectl create namespace toolhive
kubectl apply -f deployment.yamlVerify the deployment:
kubectl -n toolhive get pods
kubectl -n toolhive logs -l app=registry-apiThe Registry Server uses PostgreSQL's pgpass file for database authentication. Create a secret containing the pgpass file:
Create pgpass file:
# Create pgpass file with credentials for both users
cat > pgpass <<EOF
postgres-service.database.svc.cluster.local:5432:toolhive_registry:db_app:your-secure-app-password
postgres-service.database.svc.cluster.local:5432:toolhive_registry:db_migrator:your-secure-migration-password
EOF
chmod 600 pgpassCreate Kubernetes secret:
kubectl -n toolhive create secret generic registry-db-pgpass \
--from-file=pgpass=pgpass
# Clean up local file
rm pgpassOr using YAML:
apiVersion: v1
kind: Secret
metadata:
name: registry-db-pgpass
namespace: toolhive
type: Opaque
data:
pgpass: <base64-encoded-pgpass-content>apiVersion: v1
kind: ConfigMap
metadata:
name: registry-api-config
namespace: toolhive
data:
config.yaml: |
registryName: my-registry
registries:
- name: toolhive
format: toolhive
git:
repository: https://github.com/stacklok/toolhive.git
branch: main
path: pkg/registry/data/registry.json
syncPolicy:
interval: "15m"
auth:
mode: oauth
oauth:
resourceUrl: https://registry.example.com
providers:
- name: kubernetes
issuerUrl: https://kubernetes.default.svc
audience: https://kubernetes.default.svc
caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
database:
host: postgres.database.svc.cluster.local
port: 5432
user: db_app
migrationUser: db_migrator
database: toolhive_registry
sslMode: require
maxOpenConns: 25
maxIdleConns: 5
connMaxLifetime: "5m"Note: Database passwords are provided via the pgpass file mounted from the secret, not in the config.
apiVersion: apps/v1
kind: Deployment
metadata:
name: registry-api
namespace: toolhive
spec:
replicas: 2 # Can scale horizontally with database backend
selector:
matchLabels:
app: registry-api
template:
metadata:
labels:
app: registry-api
spec:
serviceAccountName: registry-api
containers:
- name: registry-api
image: ghcr.io/stacklok/toolhive/thv-registry-api:latest
args:
- serve
- --config=/etc/registry/config.yaml
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
- name: PGPASSFILE
value: /secrets/pgpass
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /readiness
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
volumeMounts:
- name: config
mountPath: /etc/registry
readOnly: true
- name: pgpass
mountPath: /secrets
readOnly: true
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
volumes:
- name: config
configMap:
name: registry-api-config
- name: pgpass
secret:
secretName: registry-db-pgpass
defaultMode: 0600Run migrations as a separate Job before deployment:
apiVersion: batch/v1
kind: Job
metadata:
name: registry-migrate
namespace: toolhive
spec:
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: ghcr.io/stacklok/toolhive/thv-registry-api:latest
args:
- migrate
- up
- --config=/etc/registry/config.yaml
- --yes
env:
- name: PGPASSFILE
value: /secrets/pgpass
volumeMounts:
- name: config
mountPath: /etc/registry
readOnly: true
- name: pgpass
mountPath: /secrets
readOnly: true
volumes:
- name: config
configMap:
name: registry-api-config
- name: pgpass
secret:
secretName: registry-db-pgpass
defaultMode: 0600Create a service account for the registry API:
apiVersion: v1
kind: ServiceAccount
metadata:
name: registry-api
namespace: toolhive
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: registry-api
namespace: toolhive
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: registry-api
namespace: toolhive
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: registry-api
subjects:
- kind: ServiceAccount
name: registry-api
namespace: toolhiveConfiguration for accepting both external OAuth and Kubernetes service account tokens:
auth:
mode: oauth
oauth:
resourceUrl: https://registry.example.com
providers:
# External OAuth provider
- name: company-sso
issuerUrl: https://auth.company.com
audience: api://toolhive-registry
# Kubernetes service accounts
- name: kubernetes
issuerUrl: https://kubernetes.default.svc
audience: https://kubernetes.default.svc
caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crtspec:
replicas: 3 # Multiple replicas for HA
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: registry-api-pdb
namespace: toolhive
spec:
minAvailable: 1
selector:
matchLabels:
app: registry-apiapiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: registry-api-hpa
namespace: toolhive
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: registry-api
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: registry-api-netpol
namespace: toolhive
spec:
podSelector:
matchLabels:
app: registry-api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: toolhive
ports:
- protocol: TCP
port: 8080
egress:
# Allow DNS
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
# Allow database
- to:
- namespaceSelector:
matchLabels:
name: database
ports:
- protocol: TCP
port: 5432
# Allow HTTPS (for Git sync, OAuth, etc.)
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443Recommended resource requests and limits:
resources:
requests:
cpu: 100m # Minimum required
memory: 256Mi # With database backend
limits:
cpu: 1000m # Burst capacity
memory: 1Gi # Maximum memoryAdjust based on:
- Number of registries configured
- Sync frequency
- Database connection pool size
- Expected request rate
Add Prometheus annotations:
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics" # If metrics endpoint is addedSee examples/kubernetes/ directory for complete, production-ready manifests including:
- Namespace setup
- ConfigMaps and Secrets
- Deployment with HA configuration
- Service and Ingress
- RBAC configuration
- Network policies
- Monitoring setup
A Helm chart is planned for future releases. Track progress in issue #XXX.
Check logs:
kubectl -n toolhive logs -l app=registry-apiCommon issues:
- ConfigMap not found or malformed
- Secret not found or missing keys
- Database connection failure
- Invalid authentication configuration
Verify:
# Check if database is reachable
kubectl -n toolhive exec -it deployment/registry-api -- \
nc -zv postgres.database.svc.cluster.local 5432
# Check environment variables
kubectl -n toolhive exec -it deployment/registry-api -- env | grep -E 'THV_REGISTRY_|PGPASSFILE'View migration job logs:
kubectl -n toolhive logs job/registry-migrateManually run migrations:
kubectl -n toolhive exec -it deployment/registry-api -- \
thv-registry-api migrate up --config /etc/registry/config.yaml- Configuration Guide - Complete configuration reference
- Database Setup - Database configuration and migrations
- Authentication - OAuth and security configuration
- Docker Deployment - Docker and Docker Compose