Kubernetes
Deploy the gateway and PostgreSQL to any Kubernetes cluster using the Kustomize manifests in k8s/. The manifests include a bundled PostgreSQL StatefulSet for convenience — in production, you'll likely replace it with a managed database.
Prerequisites
- A Kubernetes cluster (v1.25+)
kubectlconfigured to talk to your cluster- An LLM API key (OpenAI, Anthropic, or Google)
Step 1: Build and Push the Gateway Image
The K8s deployment needs the gateway image in a registry your cluster can pull from.
git clone https://github.com/superset-studio/majordomo-gateway.git
cd majordomo-gateway
# Build the image
docker build -t majordomo-gateway .
# Tag for your registry
docker tag majordomo-gateway your-registry.example.com/majordomo-gateway:latest
# Push to registry
docker push your-registry.example.com/majordomo-gateway:latest
Then update k8s/kustomization.yaml to point to your registry:
images:
- name: majordomo-gateway
newName: your-registry.example.com/majordomo-gateway
newTag: latest
Step 2: Configure Secrets
Edit k8s/secret.yaml and replace the placeholder passwords. Values must be base64-encoded:
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: majordomo-gateway-secrets
type: Opaque
data:
MAJORDOMO_STORAGE_POSTGRES_PASSWORD: <base64-encoded-password>
MAJORDOMO_S3_ACCESS_KEY_ID: <base64-encoded-key> # optional
MAJORDOMO_S3_SECRET_ACCESS_KEY: <base64-encoded-secret> # optional
Don't commit real secrets
The k8s/secret.yaml file contains placeholder values. For production, use sealed-secrets or external-secrets-operator instead of committing secrets to version control.
Step 3: Deploy
This creates the following resources in the majordomo namespace:
| Resource | Name | Notes |
|---|---|---|
| Namespace | majordomo |
All resources live here |
| ConfigMap | majordomo-gateway-config |
Contains majordomo.yaml |
| Secret | majordomo-gateway-secrets |
DB password, optional S3 credentials |
| StatefulSet | majordomo-postgres |
Single-replica Postgres 16 with 10Gi PVC |
| Deployment | majordomo-gateway |
2 replicas with health checks |
| Service | majordomo-postgres |
ClusterIP, port 5432 |
| Service | majordomo-gateway |
ClusterIP, port 7680 |
Wait for all pods to be ready:
You should see something like:
NAME READY STATUS RESTARTS AGE
majordomo-gateway-5d4f8b7c9-abc12 1/1 Running 0 30s
majordomo-gateway-5d4f8b7c9-def34 1/1 Running 0 30s
majordomo-postgres-0 1/1 Running 0 45s
Step 4: Apply the Database Schema
The bundled Postgres StatefulSet starts with an empty database. Apply the schema:
kubectl -n majordomo cp schema.sql majordomo-postgres-0:/tmp/schema.sql
kubectl -n majordomo exec majordomo-postgres-0 -- \
psql -U majordomo -d majordomo -f /tmp/schema.sql
Verify the tables were created:
You should see api_keys, llm_requests, and llm_requests_metadata_keys.
Using a managed database?
If you're using RDS, Cloud SQL, or another managed PostgreSQL, apply schema.sql using psql from your local machine or a bastion host instead. Then update the ConfigMap in k8s/configmap.yaml to point storage.postgres.host at your managed instance, and remove k8s/postgres.yaml from k8s/kustomization.yaml.
Step 5: Verify the Gateway is Running
Port-forward to the gateway service:
In another terminal:
# Liveness
curl http://localhost:7680/health
# ok
# Readiness (checks DB connectivity)
curl http://localhost:7680/readyz
# {"status":"ok"}
If /readyz fails, check the gateway logs:
Step 6: Create a Majordomo API Key
kubectl -n majordomo exec -it deploy/majordomo-gateway -- \
/app/majordomo-proxy keys create --name "My Team"
Output:
API Key created successfully!
ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Name: My Team
Key: mdm_sk_abc123def456...
⚠️ Save this key now - it cannot be retrieved later.
Step 7: Send a Test Request
With the port-forward still running:
curl -X POST http://localhost:7680/v1/chat/completions \
-H "Content-Type: application/json" \
-H "X-Majordomo-Key: mdm_sk_your_key_here" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "Say hello"}]
}'
You should get a normal OpenAI response back.
Step 8: Verify Logging
kubectl -n majordomo exec majordomo-postgres-0 -- \
psql -U majordomo -d majordomo -c \
"SELECT model, input_tokens, output_tokens, total_cost FROM llm_requests ORDER BY created_at DESC LIMIT 1;"
You should see one row with the model name, token counts, and calculated cost.
Exposing the Gateway
The default Service type is ClusterIP, so the gateway is only reachable from within the cluster. To expose it:
Option A: LoadBalancer — edit k8s/service.yaml:
Option B: Ingress — create an Ingress resource pointing to the majordomo-gateway service on port 7680.
Option C: Internal only — keep ClusterIP and have your applications connect via majordomo-gateway.majordomo.svc.cluster.local:7680.
Production Considerations
- Database: The included StatefulSet is suitable for evaluation. For production, use a managed PostgreSQL (RDS, Cloud SQL, AlloyDB) and remove
k8s/postgres.yamlfromkustomization.yaml. - Replicas and resources: Adjust
replicas,resources.requests, andresources.limitsink8s/deployment.yamlbased on your traffic. - Schema migrations: On upgrades, check the changelog for schema changes and apply them with
psql -f schema.sql(the schema usesIF NOT EXISTSso it's safe to re-run, but new columns may requireALTER TABLE).
Next Steps
- Distribute the Majordomo API key to your team (see the Getting Started guide for SDK integration examples)
- Enable S3 body storage for full request/response capture