Skip to Content
Deploy & Operate☸️ Deploying on Kubernetes

Deploying DocsGPT on Kubernetes

The manifests in deployment/k8s run DocsGPT with Kustomize. They start:

ResourceWhat it does
docsgpt-api Deployment and docsgpt-api-serviceThe 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 DeploymentThe Celery worker with the beat scheduler: ingestion, document parsing, query embeddings and scheduled tasks.
postgres Deployment, Service and a 5 GiB volumePostgres 16 with pgvector. It holds user data and the vector index.
redis Deployment and redis-serviceTask queue, results, the LLM cache and live event streams.
postgres-init JobMigrates the database schema.
docsgpt-secrets SecretThe 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:

PathApplied by defaultContents
kustomization.yamlYesThe list of resources kubectl apply -k creates.
docsgpt-secrets.yamlYesSettings and keys. You fill it in before applying.
deployments/, services/Yes, except Qdrant and the sandboxThe API, worker, Postgres and Redis.
jobs/postgres-init-job.yamlYesThe migration Job.
ingress-example.yamlNoAn Ingress with TLS for publishing DocsGPT.
deployments/qdrant-deploy.yaml, services/qdrant-service.yamlNoAn in-cluster Qdrant, if you prefer it to pgvector.
deployments/sandbox-deploy.yaml, network-policies/NoThe code-execution sandbox.
optional-mongo/NoMongoDB, 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

  1. Clone the repository.

    git clone https://github.com/arc53/DocsGPT.git cd DocsGPT
  2. Fill in the secrets. Open deployment/k8s/docsgpt-secrets.yaml. The values are plain text (stringData); Kubernetes encodes them. Replace every REPLACE_ME:

    KeyValue
    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_URL and S3_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 32

    Then choose the model provider. LLM_PROVIDER: docsgpt sends every chat to DocsGPT’s hosted API. To use your own, set LLM_PROVIDER, its key in API_KEY and the model in LLM_NAME; see Cloud LLM providers and Local inference. Any other setting from the settings reference can go in the same file.

    Leave AUTH_TYPE commented out only while DocsGPT is reachable through kubectl port-forward alone. See Publish DocsGPT.

    ⚠️

    The file now holds real credentials. Keep your copy out of version control.

  3. 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 later kubectl command.

  4. 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-worker

    The API and worker pods don’t migrate the database themselves. Their wait-for-migrations init container holds them until the Job has brought the schema up to what their image needs.

  5. Open DocsGPT.

    kubectl port-forward service/docsgpt-api-service 7091:80

    Then 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-secrets

The 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-worker

Storage

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

KeyValue
S3_BUCKET_NAMEThe bucket. Create it before deploying.
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEYCredentials for the bucket. To use the pod’s cloud identity (EKS IRSA, GKE Workload Identity) instead, delete both lines.
S3_REGIONThe bucket’s AWS region. Use auto for Cloudflare R2.
S3_ENDPOINT_URLFor 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:

  1. In docsgpt-secrets.yaml, set STORAGE_TYPE: local and delete the S3_* lines.

  2. Create a ReadWriteMany PersistentVolumeClaim, and in deployments/docsgpt-deploy.yaml mount it on both the docsgpt-api and docsgpt-worker containers:

    # 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:

  1. Set AUTH_TYPE in docsgpt-secrets.yaml. Use oidc for separate accounts, with OIDC_ISSUER, OIDC_CLIENT_ID and OIDC_FRONTEND_URL set to your public https:// address; see SSO with OIDC and the Security checklist.

  2. Set API_URL in docsgpt-secrets.yaml to the same public address, for example https://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 at http://localhost:7091, which works only through port-forward. The worker keeps its own in-cluster API_URL from docsgpt-deploy.yaml.

  3. 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
  4. Copy deployment/k8s/ingress-example.yaml, replace docsgpt.example.com with your host, set ingressClassName to your ingress controller, and provide the docsgpt-tls certificate (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

  1. Back up the database. Migrations can’t be undone without a backup:

    kubectl exec deploy/postgres -- pg_dump -U docsgpt docsgpt > docsgpt-backup.sql
  2. Change every arc53/docsgpt:<version> image in deployments/docsgpt-deploy.yaml and jobs/postgres-init-job.yaml to the new release, and read its notes in Upgrading.

  3. 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.sql

    Then set the older image tags, delete the Job and apply as in step 3; apply scales 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:

  1. Create the gateway token:

    kubectl create secret generic docsgpt-sandbox-gateway --from-literal=token="$(openssl rand -hex 32)"
  2. Add the runner settings to both the docsgpt-api and docsgpt-worker containers in deployments/docsgpt-deploy.yaml, under env::

    - 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
  3. 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
SymptomCause
Pods in Init:Error or Init:CrashLoopBackOffThe 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-migrationsThe 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 ingestCheck the worker log for S3 errors: bucket name, credentials, S3_ENDPOINT_URL and S3_PATH_STYLE.
Answers ignore your documents after a long waitNo worker is consuming the embeddings queue. Check that the worker pod is running.