Deploying DocsGPT on Kubernetes
The manifests in deployment/k8s run DocsGPT with Kustomize. They start:
| Resource | What it does |
|---|---|
docsgpt-api Deployment and docsgpt-api-service | The API, which also serves the web UI. The Service is ClusterIP, so nothing is reachable from outside the cluster until you publish it. |
docsgpt-worker Deployment | The Celery worker with the beat scheduler: ingestion, document parsing, query embeddings and scheduled tasks. |
postgres Deployment, Service and a 5 GiB volume | Postgres 16 with pgvector. It holds user data and the vector index. |
redis Deployment and redis-service | Task queue, results, the LLM cache and live event streams. |
postgres-init Job | Migrates the database schema. |
docsgpt-secrets Secret | The settings every DocsGPT pod reads. |
Uploaded files go to an S3-compatible bucket that you provide.
Prerequisites
- kubectlΒ and access to a cluster with a default StorageClass.
- An S3-compatible bucket and credentials for it: AWS S3, MinIO, Cloudflare R2, Backblaze B2, DigitalOcean Spaces or similar. To use a shared volume instead, see Storage.
openssl, to generate keys.
Folder structure
deployment/k8s/ contains:
| Path | Applied by default | Contents |
|---|---|---|
kustomization.yaml | Yes | The list of resources kubectl apply -k creates. |
docsgpt-secrets.yaml | Yes | Settings and keys. You fill it in before applying. |
deployments/, services/ | Yes, except Qdrant and the sandbox | The API, worker, Postgres and Redis. |
jobs/postgres-init-job.yaml | Yes | The migration Job. |
ingress-example.yaml | No | An Ingress with TLS for publishing DocsGPT. |
deployments/qdrant-deploy.yaml, services/qdrant-service.yaml | No | An in-cluster Qdrant, if you prefer it to pgvector. |
deployments/sandbox-deploy.yaml, network-policies/ | No | The code-execution sandbox. |
optional-mongo/ | No | MongoDB, for VECTOR_STORE=mongodb. |
The manifests pin every image. arc53/docsgpt is pinned to the release the manifests were written for; the image: lines in deployments/docsgpt-deploy.yaml show which, and Upgrading covers moving to another.
Deploy
-
Clone the repository.
git clone https://github.com/arc53/DocsGPT.git cd DocsGPT -
Fill in the secrets. Open
deployment/k8s/docsgpt-secrets.yaml. The values are plain text (stringData); Kubernetes encodes them. Replace everyREPLACE_ME:Key Value INTERNAL_KEYOutput of openssl rand -hex 32. The worker uses it to hand indexes to the API. Anyone holding it can read and write every userβs files through the internal endpoints.JWT_SECRET_KEYAnother openssl rand -hex 32. It signs session tokens.ENCRYPTION_SECRET_KEYAnother openssl rand -hex 32. It encrypts stored tool and connector credentials. Keep it: changing it later makes those credentials unreadable.S3_BUCKET_NAME,S3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEYYour bucket and its credentials. See Storage for S3_REGION,S3_ENDPOINT_URLandS3_PATH_STYLE.POSTGRES_PASSWORDOutput of openssl rand -hex 24. The bundled Postgres sets it when its volume is first created.POSTGRES_URIPut the same password in place of REPLACE_ME. For an external database, point it there instead.Generate each key separately:
openssl rand -hex 32Then choose the model provider.
LLM_PROVIDER: docsgptsends every chat to DocsGPTβs hosted API. To use your own, setLLM_PROVIDER, its key inAPI_KEYand the model inLLM_NAME; see Cloud LLM providers and Local inference. Any other setting from the settings reference can go in the same file.Leave
AUTH_TYPEcommented out only while DocsGPT is reachable throughkubectl port-forwardalone. See Publish DocsGPT.β οΈThe file now holds real credentials. Keep your copy out of version control.
-
Apply the manifests from the repository root:
kubectl apply -k deployment/k8s/To use a namespace, create it and add
-n <namespace>to this and every laterkubectlcommand. -
Wait for the migration and the pods.
kubectl wait --for=condition=complete job/postgres-init --timeout=300s kubectl rollout status deployment/docsgpt-api kubectl rollout status deployment/docsgpt-workerThe API and worker pods donβt migrate the database themselves. Their
wait-for-migrationsinit container holds them until the Job has brought the schema up to what their image needs. -
Open DocsGPT.
kubectl port-forward service/docsgpt-api-service 7091:80Then open http://localhost:7091Β .
Every DocsGPT pod starts with a check-secrets init container. While any value in docsgpt-secrets still contains REPLACE_ME, a required key is empty, the bucket is missing with STORAGE_TYPE: s3, or the password in POSTGRES_URI differs from POSTGRES_PASSWORD, the pods stop in Init:Error or Init:CrashLoopBackOff, and the log names what to fix:
kubectl logs job/postgres-init -c check-secretsThe Job gives up after its backoffLimit of retries, a few minutes, and then stays failed. After fixing the secret, delete the Job, apply again and restart the pods:
kubectl delete job postgres-init --ignore-not-found
kubectl apply -k deployment/k8s/
kubectl rollout restart deployment/docsgpt-api deployment/docsgpt-workerStorage
The API saves an upload, and the worker, in another pod, parses and indexes it. Both need the same files, and the index must survive pod restarts and be shared by every replica. Local disk inside a pod gives neither, so the manifests set STORAGE_TYPE: s3 and VECTOR_STORE: pgvector.
Files: an S3-compatible bucket
| Key | Value |
|---|---|
S3_BUCKET_NAME | The bucket. Create it before deploying. |
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY | Credentials for the bucket. To use the podβs cloud identity (EKS IRSA, GKE Workload Identity) instead, delete both lines. |
S3_REGION | The bucketβs AWS region. Use auto for Cloudflare R2. |
S3_ENDPOINT_URL | For services other than AWS, for example https://<account>.r2.cloudflarestorage.com or your MinIO URL. |
S3_PATH_STYLE | "true" for most services other than AWS, including MinIO. |
File and artifact downloads are streamed through the API (URL_STRATEGY: backend, the default). Agent images are the exception: the API redirects the browser to a short-lived presigned bucket URL, so browsers must be able to reach the bucketβs endpoint to show them. The S3 storage settings list the bucket permissions DocsGPT needs.
Vectors: pgvector
The bundled Postgres runs the pgvector/pgvector image, and pgvector uses the same database as POSTGRES_URI. To keep vectors in a managed Postgres with pgvector, set PGVECTOR_CONNECTION_STRING to it. To use Qdrant, apply deployments/qdrant-deploy.yaml and services/qdrant-service.yaml, and set VECTOR_STORE: qdrant and QDRANT_URL: http://qdrant:6333. The other stores and their settings are in the settings reference.
Alternative: a shared volume
If your cluster has a ReadWriteMany StorageClass (NFS, Amazon EFS, Azure Files, CephFS), you can keep files on it instead of in a bucket:
-
In
docsgpt-secrets.yaml, setSTORAGE_TYPE: localand delete theS3_*lines. -
Create a
ReadWriteManyPersistentVolumeClaim, and indeployments/docsgpt-deploy.yamlmount it on both thedocsgpt-apianddocsgpt-workercontainers:# in each container volumeMounts: - name: docsgpt-data mountPath: /app/inputs subPath: inputs - name: docsgpt-data mountPath: /app/indexes subPath: indexes # in each pod spec securityContext: fsGroup: 999 # the image's appuser; the volume must be writable by it volumes: - name: docsgpt-data persistentVolumeClaim: claimName: docsgpt-data
Keep VECTOR_STORE: pgvector with a shared volume.
Publish DocsGPT
With no AUTH_TYPE, anyone who can reach DocsGPT uses it as the same user, with access to every conversation, source and connected service. Before you publish it:
-
Set
AUTH_TYPEindocsgpt-secrets.yaml. Useoidcfor separate accounts, withOIDC_ISSUER,OIDC_CLIENT_IDandOIDC_FRONTEND_URLset to your publichttps://address; see SSO with OIDC and the Security checklist. -
Set
API_URLindocsgpt-secrets.yamlto the same public address, for examplehttps://docsgpt.example.com. The API builds agent image URLs, agent webhook URLs, the device pairing address and the MCP OAuth callback from it. Unset, they point athttp://localhost:7091, which works only throughport-forward. The worker keeps its own in-clusterAPI_URLfromdocsgpt-deploy.yaml. -
Apply the secret and restart the pods, which read it only when they start:
kubectl apply -k deployment/k8s/ kubectl rollout restart deployment/docsgpt-api deployment/docsgpt-worker -
Copy
deployment/k8s/ingress-example.yaml, replacedocsgpt.example.comwith your host, setingressClassNameto your ingress controller, and provide thedocsgpt-tlscertificate (the fileβs comments cover cert-manager). Then apply it:kubectl apply -f ingress-example.yaml
The exampleβs annotations are for ingress-nginx, which is retired. They raise the request body limit for uploads and keep streamed answers open and unbuffered. Other ingress controllers, and the Gateway API with an HTTPRoute to docsgpt-api-service port 80, work too with their own equivalent settings. The web UI calls the API on the host it was loaded from, so one host serves both.
Scaling and scheduled tasks
You can raise replicas on docsgpt-api and docsgpt-worker. At least one worker must always run: it ingests documents, embeds every search query for the API, and runs the beat scheduler (-B). The scheduler fires scheduled agent runs, source syncs, reconciliation, cleanups and the version check. Running it on every worker replica is safe, because a lock in Redis lets only one of them schedule at a time.
For heavy OCR parsing, run another worker Deployment with -Q parsing and the resources it needs.
Upgrading
-
Back up the database. Migrations canβt be undone without a backup:
kubectl exec deploy/postgres -- pg_dump -U docsgpt docsgpt > docsgpt-backup.sql -
Change every
arc53/docsgpt:<version>image indeployments/docsgpt-deploy.yamlandjobs/postgres-init-job.yamlto the new release, and read its notes in Upgrading. -
Run the migration and roll the pods:
kubectl delete job postgres-init --ignore-not-found kubectl apply -k deployment/k8s/ kubectl wait --for=condition=complete job/postgres-init --timeout=300s kubectl rollout status deployment/docsgpt-api kubectl rollout status deployment/docsgpt-worker
A finished Job canβt be changed, so kubectl apply alone leaves the old one in place and reports field is immutable for it. Deleting it first lets the new image run the migrations. The new API and worker pods wait for them, and the old pods keep serving until the new ones are ready.
Rolling back
wait-for-migrations waits until the schema is at exactly the revision its image ships. Pods of an older release therefore wait forever on a database the newer release has migrated, logging Waiting for job/postgres-init: schema at <newer>, this image needs <older>. To roll back, first bring the database back to the older revision, then set the older image tags and apply:
-
Restore the backup taken before the upgrade. Anything written since the upgrade is lost:
kubectl scale deployment/docsgpt-api deployment/docsgpt-worker --replicas=0 kubectl exec deploy/postgres -- dropdb -U docsgpt docsgpt kubectl exec deploy/postgres -- createdb -U docsgpt docsgpt kubectl exec -i deploy/postgres -- psql -q -U docsgpt -d docsgpt < docsgpt-backup.sqlThen set the older image tags, delete the Job and apply as in step 3;
applyscales the Deployments back up. -
Or, while the newer pods still run, downgrade the schema to the revision the older image needs (from the log line above). Downgrades can drop data that only the newer release stores:
kubectl exec deploy/docsgpt-api -- alembic -c docsgpt/alembic.ini downgrade <older-revision>
Optional: code-execution sandbox
The Artifact and Code Executor tools run code in a separate runner, deployments/sandbox-deploy.yaml. It is not part of the default stack. To enable it:
-
Create the gateway token:
kubectl create secret generic docsgpt-sandbox-gateway --from-literal=token="$(openssl rand -hex 32)" -
Add the runner settings to both the
docsgpt-apianddocsgpt-workercontainers indeployments/docsgpt-deploy.yaml, underenv::- name: SANDBOX_GATEWAY_URL value: "http://docsgpt-sandbox:8888" - name: SANDBOX_KERNEL_NAME value: "docsgpt-python" - name: SANDBOX_GATEWAY_AUTH_TOKEN valueFrom: secretKeyRef: name: docsgpt-sandbox-gateway key: token -
Apply the runner, its NetworkPolicy and the updated Deployments:
kubectl apply -f deployment/k8s/deployments/sandbox-deploy.yaml kubectl apply -f deployment/k8s/network-policies/sandbox-egress-policy.yaml kubectl apply -k deployment/k8s/
The NetworkPolicy blocks the runner from private addresses and cloud metadata. It takes effect only with a network plugin that enforces NetworkPolicy, such as Calico or Cilium. All sessions share one runner pod and are separated only by working directory, so read the trust notes at the top of sandbox-deploy.yaml before offering the tools to users you donβt trust. See Code Execution Sandbox for the settings and the other ways to run a sandbox, and Artifacts and code execution for the tools themselves.
Troubleshooting
kubectl get pods
kubectl logs deployment/docsgpt-api
kubectl logs deployment/docsgpt-worker
kubectl logs job/postgres-init| Symptom | Cause |
|---|---|
Pods in Init:Error or Init:CrashLoopBackOff | The secret is not filled in. kubectl logs <pod> -c check-secrets says what to fix. |
Pods stay in Init with Waiting for job/postgres-init in kubectl logs <pod> -c wait-for-migrations | The migration Job has not run for this image: check kubectl logs job/postgres-init, or delete and re-apply the Job as in Upgrading. If the schema is newer than the image needs, see Rolling back. |
| Uploads fail to ingest | Check the worker log for S3 errors: bucket name, credentials, S3_ENDPOINT_URL and S3_PATH_STYLE. |
| Answers ignore your documents after a long wait | No worker is consuming the embeddings queue. Check that the worker pod is running. |