first commit

This commit is contained in:
sttlab
2026-05-09 06:42:45 +00:00
commit 780e25c15e
16 changed files with 552 additions and 0 deletions
+142
View File
@@ -0,0 +1,142 @@
# LibreChat — Kubernetes Deployment
Deploys [LibreChat](https://www.librechat.ai/) on Kubernetes with MongoDB, MeiliSearch, and the JIRA MCP server.
## Architecture
```
Namespace: librechat
├── Ingress nginx (librechat.sttlab.pc — TLS wildcard *.sttlab.pc)
│ └── librechat :3080
│ ├── mongodb :27017 (StatefulSet, 5 Gi PVC)
│ └── meilisearch :7700 (Deployment, 2 Gi PVC)
└── MCP JIRA (namespace: mcp)
└── mcp-jira :9000 (streamable-http, 49 tools)
```
| Component | Image | Storage |
|-----------|-------|---------|
| LibreChat | `ghcr.io/danny-avila/librechat:latest` | — (stateless) |
| MongoDB | `mongo:7` | 5 Gi PVC |
| MeiliSearch | `getmeili/meilisearch:v1.7` | 2 Gi PVC |
## Directory structure
```
k8s/
├── namespace.yaml Namespace definition
├── secret.yaml Credentials (gitignored — never commit real values)
├── configmap.yaml Application environment config
├── kustomization.yaml Kustomize entry point
├── mongodb/
│ ├── pvc.yaml
│ ├── statefulset.yaml
│ └── service.yaml
├── meilisearch/
│ ├── pvc.yaml
│ ├── deployment.yaml
│ └── service.yaml
├── librechat/
│ ├── configmap-app.yaml Mounts librechat.yaml (MCP config, allowedDomains...)
│ ├── deployment.yaml
│ └── service.yaml
└── ingress/
├── ingress.yaml
└── tls.yaml TLS secret (gitignored — never commit)
```
## Prerequisites
- Kubernetes cluster with an Nginx ingress controller
- A default StorageClass (for PVCs)
- `kubectl` configured against your cluster
- Wildcard TLS certificate for `*.sttlab.pc` in `$HOME/tls/`
## Initial deployment
### 1. Fill in secrets
Edit `secret.yaml` with real values. Generate them with:
```bash
openssl rand -base64 24 # MONGO_PASSWORD
openssl rand -base64 32 # MEILI_MASTER_KEY, JWT_SECRET, JWT_REFRESH_SECRET
openssl rand -hex 32 # CREDS_KEY
openssl rand -hex 8 # CREDS_IV
```
**Important:** `MONGO_URI` must include `?authSource=admin` because `MONGO_INITDB_ROOT_USERNAME` creates the user in the `admin` database:
```
mongodb://librechat:<password>@mongodb:27017/LibreChat?authSource=admin
```
### 2. Create the TLS secret
```bash
kubectl apply -f k8s/namespace.yaml
kubectl create secret tls sttlab-tls \
--cert=$HOME/tls/sttlab.pc.crt \
--key=$HOME/tls/sttlab.pc.key \
--namespace=librechat \
--dry-run=client -o yaml > k8s/ingress/tls.yaml
kubectl apply -f k8s/ingress/tls.yaml
```
### 3. Deploy
```bash
export DOMAIN=librechat.sttlab.pc
kubectl kustomize k8s/ | envsubst '${DOMAIN}' | kubectl apply -f -
```
> **Note:** `DOMAIN` must be exported before the pipe. An inline assignment (`DOMAIN=x cmd1 | cmd2`) does not propagate to `envsubst`.
### 4. Verify
```bash
kubectl get pods,ingress -n librechat
curl -sk https://librechat.sttlab.pc/health
```
## MCP JIRA
The JIRA MCP server runs in the `mcp` namespace (`mcp-jira:9000`, `streamable-http` transport).
`librechat/configmap-app.yaml` mounts `/app/librechat.yaml` into the container:
```yaml
mcpSettings:
allowedDomains:
- "http://mcp-jira.mcp.svc.cluster.local:9000"
mcpServers:
jira:
type: streamable-http
url: http://mcp-jira.mcp.svc.cluster.local:9000/mcp
```
> `mcpSettings.allowedDomains` is required to allow internal k8s domains, which are blocked by default as SSRF protection.
## Updating
```bash
export DOMAIN=librechat.sttlab.pc
kubectl kustomize k8s/ | envsubst '${DOMAIN}' | kubectl apply -f -
```
Force a pod restart:
```bash
kubectl rollout restart deployment/librechat -n librechat
```
## Teardown
```bash
kubectl delete namespace librechat
```
> This also deletes all PVCs and their data. Back up MongoDB first if needed.
+19
View File
@@ -0,0 +1,19 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: librechat-config
namespace: librechat
data:
HOST: "0.0.0.0"
PORT: "3080"
DOMAIN_CLIENT: "https://${DOMAIN}"
DOMAIN_SERVER: "https://${DOMAIN}"
MEILI_HOST: "http://meilisearch:7700"
NODE_ENV: "production"
ALLOW_EMAIL_LOGIN: "true"
ALLOW_REGISTRATION: "true"
ALLOW_SOCIAL_LOGIN: "false"
ALLOW_SOCIAL_REGISTRATION: "false"
SESSION_EXPIRY: "900000"
REFRESH_TOKEN_EXPIRY: "604800000"
DEBUG_LOGGING: "false"
+31
View File
@@ -0,0 +1,31 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: librechat
namespace: librechat
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "20m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
spec:
ingressClassName: nginx
tls:
- hosts:
- librechat.sttlab.pc
secretName: sttlab-tls
rules:
- host: librechat.sttlab.pc
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: librechat
port:
number: 80
+18
View File
@@ -0,0 +1,18 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- secret.yaml
- configmap.yaml
- mongodb/pvc.yaml
- mongodb/statefulset.yaml
- mongodb/service.yaml
- meilisearch/pvc.yaml
- meilisearch/deployment.yaml
- meilisearch/service.yaml
- librechat/configmap-app.yaml
- librechat/deployment.yaml
- librechat/service.yaml
- ingress/tls.yaml
- ingress/ingress.yaml
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: librechat-app-config
namespace: librechat
data:
librechat.yaml: |
version: 1.3.9
mcpSettings:
allowedDomains:
- "http://mcp-jira.mcp.svc.cluster.local:9000"
mcpServers:
jira:
type: streamable-http
url: http://mcp-jira.mcp.svc.cluster.local:9000/mcp
+110
View File
@@ -0,0 +1,110 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: librechat
namespace: librechat
spec:
replicas: 1
selector:
matchLabels:
app: librechat
template:
metadata:
labels:
app: librechat
spec:
initContainers:
- name: wait-for-mongodb
image: busybox:1.36
command:
- sh
- -c
- |
until nc -z mongodb 27017; do
echo "Waiting for MongoDB..."; sleep 3;
done
- name: wait-for-meilisearch
image: busybox:1.36
command:
- sh
- -c
- |
until nc -z meilisearch 7700; do
echo "Waiting for MeiliSearch..."; sleep 3;
done
containers:
- name: librechat
image: ghcr.io/danny-avila/librechat:latest
ports:
- containerPort: 3080
envFrom:
- configMapRef:
name: librechat-config
env:
- name: MONGO_URI
valueFrom:
secretKeyRef:
name: librechat-secrets
key: MONGO_URI
- name: MEILI_MASTER_KEY
valueFrom:
secretKeyRef:
name: librechat-secrets
key: MEILI_MASTER_KEY
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: librechat-secrets
key: JWT_SECRET
- name: JWT_REFRESH_SECRET
valueFrom:
secretKeyRef:
name: librechat-secrets
key: JWT_REFRESH_SECRET
- name: CREDS_KEY
valueFrom:
secretKeyRef:
name: librechat-secrets
key: CREDS_KEY
- name: CREDS_IV
valueFrom:
secretKeyRef:
name: librechat-secrets
key: CREDS_IV
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: librechat-secrets
key: OPENAI_API_KEY
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: librechat-secrets
key: ANTHROPIC_API_KEY
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /health
port: 3080
initialDelaySeconds: 30
periodSeconds: 20
readinessProbe:
httpGet:
path: /health
port: 3080
initialDelaySeconds: 20
periodSeconds: 10
volumeMounts:
- name: app-config
mountPath: /app/librechat.yaml
subPath: librechat.yaml
volumes:
- name: app-config
configMap:
name: librechat-app-config
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: librechat
namespace: librechat
spec:
selector:
app: librechat
ports:
- port: 80
targetPort: 3080
+56
View File
@@ -0,0 +1,56 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: meilisearch
namespace: librechat
spec:
replicas: 1
selector:
matchLabels:
app: meilisearch
template:
metadata:
labels:
app: meilisearch
spec:
containers:
- name: meilisearch
image: getmeili/meilisearch:v1.7
ports:
- containerPort: 7700
env:
- name: MEILI_MASTER_KEY
valueFrom:
secretKeyRef:
name: librechat-secrets
key: MEILI_MASTER_KEY
- name: MEILI_ENV
value: production
- name: MEILI_DB_PATH
value: /meili_data
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 300m
memory: 256Mi
volumeMounts:
- name: data
mountPath: /meili_data
livenessProbe:
httpGet:
path: /health
port: 7700
initialDelaySeconds: 20
periodSeconds: 20
readinessProbe:
httpGet:
path: /health
port: 7700
initialDelaySeconds: 10
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: meilisearch-data
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: meilisearch-data
namespace: librechat
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: meilisearch
namespace: librechat
spec:
selector:
app: meilisearch
ports:
- port: 7700
targetPort: 7700
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-data
namespace: librechat
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: mongodb
namespace: librechat
spec:
selector:
app: mongodb
ports:
- port: 27017
targetPort: 27017
clusterIP: None
+61
View File
@@ -0,0 +1,61 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongodb
namespace: librechat
spec:
serviceName: mongodb
replicas: 1
selector:
matchLabels:
app: mongodb
template:
metadata:
labels:
app: mongodb
spec:
containers:
- name: mongodb
image: mongo:7
ports:
- containerPort: 27017
env:
- name: MONGO_INITDB_ROOT_USERNAME
value: librechat
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: librechat-secrets
key: MONGO_PASSWORD
- name: MONGO_INITDB_DATABASE
value: LibreChat
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumeMounts:
- name: data
mountPath: /data/db
livenessProbe:
exec:
command:
- mongosh
- --eval
- "db.adminCommand('ping')"
initialDelaySeconds: 30
periodSeconds: 20
readinessProbe:
exec:
command:
- mongosh
- --eval
- "db.adminCommand('ping')"
initialDelaySeconds: 15
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: mongodb-data
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: librechat
+17
View File
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Secret
metadata:
name: librechat-secrets
namespace: librechat
type: Opaque
stringData:
MONGO_PASSWORD: "CHANGE_ME_MONGO_PASSWORD"
MONGO_URI: "mongodb://librechat:CHANGE_ME_MONGO_PASSWORD@mongodb:27017/LibreChat"
MEILI_MASTER_KEY: "CHANGE_ME_MEILI_MASTER_KEY_MIN_16_CHARS"
JWT_SECRET: "CHANGE_ME_JWT_SECRET_MIN_32_CHARS_RANDOM"
JWT_REFRESH_SECRET: "CHANGE_ME_JWT_REFRESH_SECRET_MIN_32_CHARS"
CREDS_KEY: "CHANGE_ME_32_BYTE_HEX_KEY_0000000000000000000000000000"
CREDS_IV: "CHANGE_ME_16BYTE"
# Optional: fill in your AI provider API keys
OPENAI_API_KEY: ""
ANTHROPIC_API_KEY: ""