PageSourceSearch

https://learn.emailengine.app/assets/js/04da809a.0afaf560.js

js emailengine.app collected 2026-09-25 20:13:46 UTC 21,650 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkemailengine_temp=globalThis.webpackChunkemailengine_temp||[]).push([[1968],{24137(e,n,s){s.r(n),s.d(n,{assets:()=>o,contentTitle:()=>l,default:()=>h,frontMatter:()=>a,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"deployment/kubernetes","title":"Kubernetes Deployment","description":"Deploy EmailEngine on Kubernetes with production configurations","source":"@site/docs/deployment/kubernetes.md","sourceDirName":"deployment","slug":"/deployment/kubernetes","permalink":"/docs/deployment/kubernetes","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":2,"frontMatter":{"title":"Kubernetes Deployment","description":"Deploy EmailEngine on Kubernetes with production configurations","sidebar_position":2},"sidebar":"docsSidebar","previous":{"title":"Deployment Overview","permalink":"/docs/deployment/"},"next":{"title":"SystemD Service","permalink":"/docs/deployment/systemd"}}');var r=s(74848),t=s(28453);const a={title:"Kubernetes Deployment",description:"Deploy EmailEngine on Kubernetes with production configurations",sidebar_position:2},l="Kubernetes Deployment",o={},c=[{value:"Overview",id:"overview",level:2},{value:"Prerequisites",id:"prerequisites",level:2},{value:"Basic Deployment",id:"basic-deployment",level:2},{value:"EmailEngine Deployment",id:"emailengine-deployment",level:3},{value:"Secrets Management",id:"secrets-management",level:3},{value:"Redis StatefulSet",id:"redis-statefulset",level:3},{value:"Deploy to Kubernetes",id:"deploy-to-kubernetes",level:2},{value:"Production Configuration",id:"production-configuration",level:2},{value:"Resource Tuning",id:"resource-tuning",level:3},{value:"Ingress Configuration",id:"ingress-configuration",level:3},{value:"Update Strategy",id:"update-strategy",level:3},{value:"Monitoring",id:"monitoring",level:2},{value:"Prometheus ServiceMonitor",id:"prometheus-servicemonitor",level:3},{value:"Health Check Endpoints",id:"health-check-endpoints",level:3},{value:"Troubleshooting",id:"troubleshooting",level:2},{value:"Check Pod Status",id:"check-pod-status",level:3},{value:"Common Issues",id:"common-issues",level:3},{value:"See Also",id:"see-also",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"kubernetes-deployment",children:"Kubernetes Deployment"})}),"\n",(0,r.jsx)(n.p,{children:"Deploy EmailEngine on Kubernetes for container orchestration and cloud-native deployments."}),"\n",(0,r.jsx)(n.admonition,{title:"Docker Basics",type:"tip",children:(0,r.jsxs)(n.p,{children:["For Docker and Docker Compose setup, see the ",(0,r.jsx)(n.a,{href:"/docs/installation/docker",children:"Docker Installation Guide"}),". This page covers Kubernetes-specific deployment."]})}),"\n",(0,r.jsx)(n.admonition,{title:"Single Instance Only",type:"warning",children:(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"EmailEngine does NOT support horizontal scaling."})," You must run exactly ",(0,r.jsx)(n.strong,{children:"one replica"})," of EmailEngine per Redis database. Two instances on the same Redis database each open their own IMAP connections for every account, deliver every webhook twice, and overwrite each other's sync state. Scale vertically (more CPU/RAM) instead of horizontally (more replicas)."]})}),"\n",(0,r.jsx)(n.h2,{id:"overview",children:"Overview"}),"\n",(0,r.jsx)(n.p,{children:"Kubernetes deployment provides:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Self-healing"})," - Automatic pod replacement on failure"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Controlled updates"})," - A ",(0,r.jsx)(n.code,{children:"Recreate"})," rollout that never overlaps two instances (see ",(0,r.jsx)(n.a,{href:"#update-strategy",children:"Update Strategy"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Service discovery"})," - A stable in-cluster address for Redis and for EmailEngine"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Secret management"})," - Credentials mounted from Secrets rather than baked into images"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Resource management"})," - CPU and memory limits"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["A Kubernetes cluster whose API serves ",(0,r.jsx)(n.code,{children:"networking.k8s.io/v1"})," Ingress (1.19 or later)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"kubectl"})," configured"]}),"\n",(0,r.jsx)(n.li,{children:"Redis accessible from cluster (or deploy Redis in cluster)"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"basic-deployment",children:"Basic Deployment"}),"\n",(0,r.jsx)(n.h3,{id:"emailengine-deployment",children:"EmailEngine Deployment"}),"\n",(0,r.jsxs)(n.p,{children:["Create ",(0,r.jsx)(n.code,{children:"emailengine-deployment.yaml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: emailengine\n  labels:\n    app: emailengine\nspec:\n  replicas: 1  # IMPORTANT: Do not increase - EmailEngine doesn\'t support horizontal scaling\n  selector:\n    matchLabels:\n      app: emailengine\n  template:\n    metadata:\n      labels:\n        app: emailengine\n    spec:\n      containers:\n      - name: emailengine\n        image: postalsys/emailengine:v2\n        ports:\n        - containerPort: 3000\n          name: http\n        env:\n        - name: EENGINE_REDIS\n          valueFrom:\n            secretKeyRef:\n              name: emailengine-secrets\n              key: redis-url\n        - name: EENGINE_SECRET\n          valueFrom:\n            secretKeyRef:\n              name: emailengine-secrets\n              key: secret\n        # The image already sets EENGINE_HOST=0.0.0.0 and EENGINE_API_PROXY=true.\n        # Name the peers allowed to set X-Forwarded-For (the ingress controller\'s pods):\n        - name: EENGINE_API_PROXY_ADDRESSES\n          value: "10.244.0.0/16"\n        resources:\n          requests:\n            memory: "1Gi"\n            cpu: "500m"\n          limits:\n            memory: "2Gi"\n            cpu: "2"\n        livenessProbe:\n          httpGet:\n            path: /health\n            port: 3000\n          initialDelaySeconds: 30\n          periodSeconds: 10\n        readinessProbe:\n          httpGet:\n            path: /health\n            port: 3000\n          initialDelaySeconds: 10\n          periodSeconds: 5\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: emailengine\nspec:\n  selector:\n    app: emailengine\n  ports:\n  - protocol: TCP\n    port: 80\n    targetPort: 3000\n  type: LoadBalancer\n'})}),"\n",(0,r.jsx)(n.h3,{id:"secrets-management",children:"Secrets Management"}),"\n",(0,r.jsxs)(n.p,{children:["Create ",(0,r.jsx)(n.code,{children:"emailengine-secrets.yaml"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'apiVersion: v1\nkind: Secret\nmetadata:\n  name: emailengine-secrets\ntype: Opaque\
1nstringData:\n  redis-url: "redis://redis:6379/8"\n  secret: "your-secret-key-at-least-32-characters"\n'})}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Create from the command line:"})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'kubectl create secret generic emailengine-secrets \\\n  --from-literal=redis-url="redis://redis:6379/8" \\\n  --from-literal=secret="$(openssl rand -hex 32)"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The keys of the Secret (",(0,r.jsx)(n.code,{children:"redis-url"}),", ",(0,r.jsx)(n.code,{children:"secret"}),") are what the Deployment's ",(0,r.jsx)(n.code,{children:"secretKeyRef"})," entries name; they do not have to match the environment variable names."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Prepared configuration"})," goes through the same Secret. Add the values as keys and map them to ",(0,r.jsx)(n.a,{href:"/docs/configuration/reset-password",children:(0,r.jsx)(n.code,{children:"EENGINE_PREPARED_PASSWORD"})}),", ",(0,r.jsx)(n.a,{href:"/docs/configuration/prepared-settings/tokens",children:(0,r.jsx)(n.code,{children:"EENGINE_PREPARED_TOKEN"})})," and ",(0,r.jsx)(n.a,{href:"/docs/configuration/prepared-settings/license",children:(0,r.jsx)(n.code,{children:"EENGINE_PREPARED_LICENSE"})})," with ",(0,r.jsx)(n.code,{children:"secretKeyRef"}),", or mount the Secret as files and point the ",(0,r.jsx)(n.code,{children:"_FILE"})," variants at them:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"        - name: EENGINE_PREPARED_LICENSE_FILE\n          value: /run/secrets/emailengine/license\n        volumeMounts:\n        - name: prepared\n          mountPath: /run/secrets/emailengine\n          readOnly: true\n      volumes:\n      - name: prepared\n        secret:\n          secretName: emailengine-secrets\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Most ",(0,r.jsx)(n.code,{children:"EENGINE_*"})," variables have a ",(0,r.jsx)(n.code,{children:"_FILE"})," form; the exceptions are listed under ",(0,r.jsx)(n.a,{href:"/docs/configuration/environment-variables#loading-values-from-files",children:"Loading values from files"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"redis-statefulset",children:"Redis StatefulSet"}),"\n",(0,r.jsx)(n.p,{children:"If running Redis in the cluster:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: redis\nspec:\n  serviceName: redis\n  replicas: 1\n  selector:\n    matchLabels:\n      app: redis\n  template:\n    metadata:\n      labels:\n        app: redis\n    spec:\n      containers:\n      - name: redis\n        image: redis:7-alpine\n        command: ["redis-server", "--save", "60 10000", "--save", "300 10", "--save", "900 1", "--maxmemory-policy", "noeviction"]\n        ports:\n        - containerPort: 6379\n        volumeMounts:\n        - name: redis-data\n          mountPath: /data\n  volumeClaimTemplates:\n  - metadata:\n      name: redis-data\n    spec:\n      accessModes: [ "ReadWriteOnce" ]\n      resources:\n        requests:\n          storage: 10Gi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: redis\nspec:\n  selector:\n    app: redis\n  ports:\n  - port: 6379\n  clusterIP: None\n'})}),"\n",(0,r.jsx)(n.h2,{id:"deploy-to-kubernetes",children:"Deploy to Kubernetes"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"# Create namespace\nkubectl create namespace emailengine\n\n# Apply configurations\nkubectl apply -f emailengine-secrets.yaml -n emailengine\nkubectl apply -f redis-statefulset.yaml -n emailengine\nkubectl apply -f emailengine-deployment.yaml -n emailengine\n\n# Check status\nkubectl get pods -n emailengine\nkubectl get services -n emailengine\n\n# View logs\nkubectl logs -f deployment/emailengine -n emailengine\n\n# Note: Do NOT scale to multiple replicas - EmailEngine doesn't support horizontal scaling\n# kubectl scale deployment/emailengine --replicas=1 -n emailengine\n"})}),"\n",(0,r.jsx)(n.h2,{id:"production-configuration",children:"Production Configuration"}),"\n",(0,r.jsx)(n.h3,{id:"resource-tuning",children:"Resource Tuning"}),"\n",(0,r.jsxs)(n.p,{children:["Adjust resources based on account count. Since EmailEngine runs as a single instance, scale ",(0,r.jsx)(n.strong,{children:"vertically"})," by increasing CPU and memory:"]}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Accounts"}),(0,r.jsx)(n.th,{children:"Memory"}),(0,r.jsx)(n.th,{children:"CPU"}),(0,r.jsx)(n.th,{children:"Notes"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"< 100"}),(0,r.jsx)(n.td,{children:"1Gi"}),(0,r.jsx)(n.td,{children:"500m"}),(0,r.jsx)(n.td,{children:"Minimum for small deployments"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"100-1000"}),(0,r.jsx)(n.td,{children:"2Gi"}),(0,r.jsx)(n.td,{children:"1"}),(0,r.jsx)(n.td,{children:"Typical small business usage"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"1000-5000"}),(0,r.jsx)(n.td,{children:"4Gi"}),(0,r.jsx)(n.td,{children:"2"}),(0,r.jsx)(n.td,{children:"Medium deployments"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"5000+"}),(0,r.jsx)(n.td,{children:"8Gi"}),(0,r.jsx)(n.td,{children:"4"}),(0,r.jsx)(n.td,{children:"Large deployments, consider dedicated Redis"})]})]})]}),"\n",(0,r.jsxs)(n.admonition,{title:"Vertical Scaling Only",type:"note",children:[(0,r.jsx)(n.p,{children:"EmailEngine does not support horizontal scaling. For very large deployments (10,000+ accounts), consider:"}),(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Running multiple EmailEngine instances with ",(0,r.jsx)(n.strong,{children:"separate Redis databases"})," (each handling different accounts)"]}),"\n",(0,r.jsx)(n.li,{children:"Using a dedicated high-performance Redis instance"}),"\n",(0,r.jsxs)(n.li,{children:["Optimizing worker count with ",(0,r.jsx)(n.code,{children:"EENGINE_WORKERS"})," environment variable"]}),"\n"]})]}),"\n",(0,r.jsx)(n.h3,{id:"ingress-configuration",children:"Ingress Configuration"}),"\n",(0,r.jsxs)(n.p,{children:["Two of EmailEngine's endpoints stream responses: ",(0,r.jsx)(n.code,{children:"/v1/changes"})," (the ",(0,r.jsx)(n.a,{href:"/docs/api/get-v-1-changes",children:"change stream"}),", also used by the admin dashboard as ",(0,r.jsx)(n.code,{children:"/admin/changes"}),") and ",(0,r.jsx)(n.code,{children:"/mcp"})," when an agent subscribes. An ingress that buffers responses holds those events until the connection closes, so turn buffering off and allow long reads. With ingress-nginx:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:'apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: emailengine-ingress\n  annotations:\n    nginx.ingress.kubernetes.io/proxy-body-size: "50m"\n    nginx.ingress.kubernetes.io/proxy-buffering: "off"\n    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"\n    cert-manager.io/cluster-issuer: "letsencrypt-prod"\nspec:\n  ingressClassName: nginx\n  tls:\n  - hosts:\n    - emailengine.example.com\n    secretName: emailengine-tls\n  rules:\n  - host: emailengine.example.com\n    http:\n      paths:\n      - path: /\n        pathType: Prefix\n        backend:\n          service:\n            name: emailengine\n            port:\n              number: 80\n'})}),"\n",(0,r.jsx)(n.h3,{id:"update-strategy",children:"Update Strategy"}),"\n",(0,r.jsxs)(n.p,{children:["Use the ",(0,r.jsx)(n.code,{children:"Recreate"})," strategy so the old pod is fully terminated before the new one starts:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: emailengine\nspec:\n  replicas: 1\n  strategy:\n    type: Recreate\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Do not use ",(0,r.jsx)(n.code,{children:"RollingUpdate"})," with ",(0,r.jsx)(n.code,{children:"maxSurge"})," - that briefly runs two EmailEngine instances against the same Redis database, which EmailEngine does not support. The ",(0,r.jsx)(n.code,{children:"Recreate"}
1)," strategy means a short downtime during each deployment, but this is the safe trade-off for a single-instance application."]}),"\n",(0,r.jsxs)(n.p,{children:["Roll out a new release by changing the image tag, for example ",(0,r.jsx)(n.code,{children:"kubectl set image deployment/emailengine emailengine=postalsys/emailengine:v2.79.4"}),". A floating tag such as ",(0,r.jsx)(n.code,{children:"v2"})," does not change the pod template, so nothing is rolled out."]}),"\n",(0,r.jsx)(n.h2,{id:"monitoring",children:"Monitoring"}),"\n",(0,r.jsx)(n.h3,{id:"prometheus-servicemonitor",children:"Prometheus ServiceMonitor"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"/metrics"})," endpoint needs an access token with the ",(0,r.jsx)(n.code,{children:"metrics"})," scope. Issue one with ",(0,r.jsx)(n.code,{children:"emailengine tokens issue -s metrics"})," (see ",(0,r.jsx)(n.a,{href:"/docs/configuration/prepared-settings/tokens",children:"Prepared Tokens"}),") and store it in the Secret under ",(0,r.jsx)(n.code,{children:"metrics-token"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"apiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: emailengine\n  labels:\n    app: emailengine\nspec:\n  selector:\n    matchLabels:\n      app: emailengine\n  endpoints:\n  - port: http\n    path: /metrics\n    interval: 30s\n    bearerTokenSecret:\n      name: emailengine-secrets\n      key: metrics-token\n"})}),"\n",(0,r.jsx)(n.h3,{id:"health-check-endpoints",children:"Health Check Endpoints"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"/health"})," needs no authentication. It returns ",(0,r.jsx)(n.code,{children:'{"success": true}'})," once the API worker is serving, every configured IMAP worker thread is running, and a Redis write-read-delete round trip succeeds; otherwise it returns a 500 whose message is ",(0,r.jsx)(n.code,{children:"Not all IMAP workers available"})," or ",(0,r.jsx)(n.code,{children:"Database check failed"}),". Nothing answers on the port until the API worker is up, which is what makes it usable for both probes:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Liveness:"})," ",(0,r.jsx)(n.code,{children:"/health"})," - a pod that keeps failing after ",(0,r.jsx)(n.code,{children:"initialDelaySeconds"})," is restarted"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Readiness:"})," ",(0,r.jsx)(n.code,{children:"/health"})," - the Service does not route to the pod until the API answers"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["The check says nothing about individual accounts: an instance whose accounts are all in ",(0,r.jsx)(n.code,{children:"authenticationError"})," is still healthy. Watch the ",(0,r.jsx)(n.code,{children:"imap_connections"})," metric or the account list for that."]}),"\n",(0,r.jsx)(n.h2,{id:"troubleshooting",children:"Troubleshooting"}),"\n",(0,r.jsx)(n.h3,{id:"check-pod-status",children:"Check Pod Status"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"# Get pod status\nkubectl get pods -n emailengine\n\n# Describe pod for events\nkubectl describe pod <pod-name> -n emailengine\n\n# Get logs\nkubectl logs <pod-name> -n emailengine\nkubectl logs <pod-name> -n emailengine --previous  # Previous container\n"})}),"\n",(0,r.jsx)(n.h3,{id:"common-issues",children:"Common Issues"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Pods not starting:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Check secrets are created: ",(0,r.jsx)(n.code,{children:"kubectl get secrets -n emailengine"})]}),"\n",(0,r.jsx)(n.li,{children:"Verify Redis connectivity from pod"}),"\n",(0,r.jsx)(n.li,{children:"Check resource limits"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"Redis connection errors:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Verify Redis service is running"}),"\n",(0,r.jsx)(n.li,{children:"Check Redis URL in secret"}),"\n",(0,r.jsxs)(n.li,{children:["Test connectivity: ",(0,r.jsx)(n.code,{children:"kubectl exec -it <pod> -- nc -zv redis 6379"})]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsx)(n.strong,{children:"High memory usage:"})}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Increase memory limits"}),"\n",(0,r.jsx)(n.li,{children:"Check account count vs resources"}),"\n",(0,r.jsx)(n.li,{children:"Review worker configuration"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"see-also",children:"See Also"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/installation/docker",children:"Docker Installation"})," - Docker and Docker Compose setup"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/deployment/nginx-proxy",children:"Nginx Reverse Proxy"})," - The same streaming and forwarded-header rules for a proxy outside the cluster"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/configuration/prepared-settings",children:"Prepared Settings"})," - Provisioning settings, tokens, the license and the password from Secrets"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/advanced/performance-tuning",children:"Performance Tuning"})," - Optimize for large deployments"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"/docs/advanced/monitoring",children:"Monitoring"})," - Set up Prometheus and Grafana"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},28453(e,n,s){s.d(n,{R:()=>a,x:()=>l});var i=s(96540);const r={},t=i.createContext(r);function a(e){const n=i.useContext(t);return i.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:a(e.components),i.createElement(t.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.