# Troubleshooting Symptoms and fixes, by deployment type. Start with the doctor script — it diagnoses most of what is listed here: ```bash # Docker all-in-one cd docker/all-in-one && ./doctor.sh # Kubernetes ./helm/doctor.sh # defaults: teable opensandbox-system ``` ## Kubernetes ### Pods stuck in `Pending` Almost always storage: the PersistentVolumeClaims need a default StorageClass. ```bash kubectl get pvc -n opensandbox-system # look for Pending claims kubectl get storageclass # is any class marked (default)? ``` Either mark a class as default or set the `storageClassName` fields in your values file (one per component, see `helm/examples/values.example.yaml`). ### Certificates never become `Ready` The chart requests certificates from a ClusterIssuer named `letsencrypt-dns` by default. If you have not created it (or named yours differently), every Certificate stays not-ready and ingress TLS serves a placeholder cert. ```bash kubectl get clusterissuer # does letsencrypt-dns exist? kubectl describe certificate -n opensandbox-system # issuer errors show here ``` Create the issuer (section 2 of `helm/examples/values.example.yaml`) or point `certificate.issuerName` at yours. Two hosts are wildcards (`*.app`, `*.sandbox`), so the issuer must use a DNS-01 solver. ### Teable crashloops with `NoSuchBucket` after `helm install --wait` timed out The storage buckets are created by a post-install hook, and Helm runs hooks only *after* `--wait` returns -- while Teable cannot become ready *without* the buckets. On a first install `--wait` therefore deadlocks until the timeout, the release is marked failed, the hook never runs, and Teable restarts with `NoSuchBucket: teable-public`. Install without `--wait` (see the quick start note). To recover an already-failed install, run the hooks once and clean them up (they are replayed outside of Helm, so Helm's hook cleanup does not apply -- the delete removes the finished hook Jobs and their temporary RBAC, keeping what they produced): ```bash helm get hooks -n opensandbox-system | kubectl apply -f - kubectl wait --for=condition=complete job/-minio-init -n opensandbox-system --timeout=5m helm get hooks -n opensandbox-system | kubectl delete -f - --ignore-not-found ``` ### `opensandbox-server` in `CrashLoopBackOff`: `BatchSandbox template file not found` Your values override `configToml` (which references `/etc/opensandbox/batchsandbox-template.yaml`) but leave `opensandbox-server.server.batchSandboxTemplate` empty, so the file is never mounted. Set the template back (the chart ships a working default), or remove the reference from your custom `configToml`. ### `CreateContainerConfigError`: `secret "git-registry-jwt" not found` The chart generates this signing keypair in a pre-install hook. If you set `gitRegistry.jwtSecret.create: false`, you must create the Secret yourself: ```bash openssl genpkey -algorithm ed25519 -out jwt.key openssl pkey -in jwt.key -pubout -out jwt.pub kubectl -n opensandbox-system create secret generic git-registry-jwt \ --from-file=private=jwt.key --from-file=public=jwt.pub ``` ### `helm upgrade` fails with `Apply failed with 1 conflict ... "kubectl-set"` You swapped an image with `kubectl set image`, and Helm 4 (server-side apply) refuses to take the field back. Add `--force-conflicts` to the upgrade — combined with `-f helm/examples/images.values.yaml` this re-pins the images and puts Helm back in charge: ```bash helm upgrade helm/teable-infra -n opensandbox-system \ --reuse-values --server-side=true --force-conflicts \ -f helm/examples/images.values.yaml ``` Seeing `forceConflicts enabled when serverSideApply disabled` instead? Your release history was installed with client-side apply; `--server-side=true` (included above) switches it over. ### Sandbox previews return 502 A 502 from `https://-.sandbox.` means routing works but nothing inside the sandbox listens on that port — check the app running in the sandbox. If the *host does not resolve*, the `*.sandbox.` DNS record is missing. ### AI features in Teable return errors Teable reaches the runtime plane at `https://infra.`. Check the chain in order: ```bash kubectl exec deploy/-teable -n opensandbox-system -- \ sh -c 'wget -q -O- https://infra./api/health' # DNS + ingress + TLS kubectl logs deploy/-teable -n opensandbox-system | tail -50 ``` A TLS verification error here means the infra certificate is not trusted by the Teable pod — see the certificates section above. ### Creating or importing a skill fails Skills are stored as files through the Infra object API, which is enabled by the `s3Compat.enabled` switch (off by default) served on top of `fileBrowser.enabled` (on by default), both under `infraService` -- see [`helm/README.md`](helm/README.md), "AI Agent skills". Ask the app pod what the API answers: ```bash kubectl exec deploy/-teable -n opensandbox-system -- node -e ' fetch(process.env.TEABLE_INFRA_API_URL + "/s3/teable-agent?list-type=2&delimiter=/&max-keys=10", { headers: { Authorization: "Bearer " + process.env.TEABLE_INFRA_API_KEY }, }).then(async r => console.log(r.status, (await r.text()).slice(0, 200))) .catch(e => console.log("request failed:", e.message))' ``` - `200` with an XML listing -- the API is healthy. - `404` saying `S3 endpoint is disabled` -- `s3Compat.enabled` is off. - `404` saying `JuiceFS mount is disabled` -- `fileBrowser.enabled` is off. The message names JuiceFS no matter which storage you actually mounted. - `404` with `NoSuchBucket` -- `s3Compat.buckets` no longer maps `teable-agent`. - `401` -- the app and Infra Service disagree on the API key, or Infra Service cannot read the `opensandbox-api-key` Secret at all (wrong namespace, missing RBAC); check the Infra Service logs to tell the two apart. - `request failed: ...` -- the app never reached the Infra host: DNS, ingress or a TLS chain it does not trust. A `200` here with saves still failing points at `fileBrowser.readOnly: true`: reads keep working while every write hits a read-only filesystem and returns a bare 500. ### Skills are saved but the agent never uses them The sandbox mounts a different volume than the one Infra Service writes to: the mount succeeds, the skills directory inside the sandbox is empty, and nothing logs an error. Both must resolve to one shared filesystem -- see [`helm/README.md`](helm/README.md), "AI Agent skills". ### AI sessions fail right after starting: `self-signed certificate in certificate chain` The stack is healthy and the UI works, but sandboxes reject the callback to your Teable/infra hosts (`SELF_SIGNED_CERT_IN_CHAIN`, `UNABLE_TO_VERIFY_LEAF_SIGNATURE`, or builds failing on `git push` with `SSL certificate problem`). Your hosts serve certificates from a private CA that the sandboxes do not trust — mount the root CA into the sandbox template: see [`helm/private-ca.md`](helm/private-ca.md). ## Docker all-in-one `./doctor.sh` covers the mainline failures (entry routing, `/v1` split, storage, sandbox engine, and in server mode the certificate names, wildcard DNS and private-CA mounts). Deploying on a private network? The checklist in [`docker/all-in-one/private-network.md`](docker/all-in-one/private-network.md) prevents most of the entries below. ### AI chat spins forever; Teable logs `SandboxReadyTimeoutException ... fetch failed` (server) The sandbox is running (its log shows `Server listening`) but the Teable app never reaches it. The app dials every sandbox at `-.sandbox.`, resolved through the machine's resolvers, and that wildcard does not resolve -- typically because the domain was "set up" in a hosts file, which cannot express wildcards and is not read by containers. Add `*.sandbox.` (and `*.app.`) on a DNS server the machine uses; `./doctor.sh` confirms with its "wildcard DNS resolves inside containers" line. ### Sandboxes exit immediately; their log shows `sudo: a password is required` The sandbox container dies within a second with `sudo: a terminal is required to read the password` / `sudo: a password is required`. Teable app images published between 2026-08-18 and 2026-09-04 started Docker-runtime sandboxes through `sudo`, while the agent image had already dropped its passwordless sudo grant -- the two changes crossed. Kubernetes deployments are not affected. **Fix: upgrade the Teable app** to `release.2026-09-07T01-58-27Z.2952` or later -- every platform release from v2026.9.9 on pins one (`./pin-image.sh`, or set `TEABLE_IMAGE` in `.env`, then `docker compose up -d teable`). Sandboxes start through the direct entrypoint again; the workspace-directory ownership the `sudo` path was meant to fix is handled by the sandbox engine (`opensandbox-server` >= `v0.2.0-fix8`, pinned here). If you cannot upgrade yet, restore the grant from the host as a stopgap: ```bash echo "agent ALL=(ALL) NOPASSWD:ALL" | sudo tee /opt/teable/sandbox-sudoers >/dev/null sudo chown root:root /opt/teable/sandbox-sudoers && sudo chmod 0440 /opt/teable/sandbox-sudoers ``` then in `.env`: ```bash SANDBOX_EXTRA_BINDS=/opt/teable/sandbox-sudoers:/etc/sudoers.d/91-agent:ro ``` and `./apply.sh server --with-app` (or `local`) followed by `docker compose up -d` (the engine is recreated with the new config). New sandboxes start again. Remove the line (and re-run the same two commands) once the fixed Teable release is running: it hands the AI agent passwordless root inside every sandbox, which the fixed release no longer needs. ### Admin sandbox check: `SELF_SIGNED_CERT_IN_CHAIN` (server) The entry's certificate is signed by a private/corporate CA and the app-side containers do not trust it. Set `PRIVATE_CA_FILE` in `.env` to the CA root certificate (PEM), re-run `./apply.sh server --with-app`, then `docker compose up -d`: the Teable app, the Infra Service and the sandbox engine are recreated with the CA. The same two commands apply after replacing the CA file's content (rotation) -- `apply.sh` records its fingerprint, so compose knows to recreate. See [`private-network.md`](docker/all-in-one/private-network.md). ### Admin sandbox check: `ERR_TLS_CERT_ALTNAME_INVALID` (server) `Host: infra. is not in the cert's altnames: ...` -- the certificate on 443 does not list that name. Usually after changing `BASE_DOMAIN` without replacing the certificate, or a certificate missing the wildcards. Replace the files behind `TLS_CERT_FILE` / `TLS_KEY_FILE` with one covering all four names, then `./apply.sh server [--with-app]` and `docker compose up -d` -- `apply.sh` records the certificate's fingerprint, so the entry is recreated (a running Caddy never re-reads a replaced file on its own). ### Wrong certificate on 443 (for example `DNS:ingress.local`) (server) `openssl s_client` against port 443 from a workstation shows a certificate that is not yours -- `ingress.local` is the default certificate of a Kubernetes ingress controller. Something else on the machine (a k3s or other Kubernetes ingress, nginx, a control panel) answers 443 for LAN clients. Testing from the machine itself does not show this: a local connection takes Docker's own forwarding and reaches the entry. Free 80/443 for the entry (disable that ingress, or use a machine of its own); the stack has no option to run on another port. ### Admin page still shows an old TLS error after the fix (server) The "version and compatibility" check runs once when the Teable app starts and keeps its last result. After fixing certificates or DNS, restart the app so it re-checks: `docker compose restart teable`. ### Browser preview URLs do not resolve (server) The `*.sandbox.` and `*.app.` wildcard DNS records are missing — both must point at the machine, DNS-only (no proxy). ### Certificate issuance fails on first start (server, automatic certificates) `CLOUDFLARE_API_TOKEN` lacks the Zone/DNS edit permission, the DNS records point somewhere else, or the machine cannot reach Let's Encrypt / the Cloudflare API outbound. Check `docker compose logs caddy` for the ACME error. On a network without that outbound access, bring your own certificate instead (`TLS_CERT_FILE` / `TLS_KEY_FILE`). ### doctor entry checks return `000` on the machine itself (server) Cloud VMs usually cannot reach their own public IP (GCP, for example, does not hairpin NAT), so every `https://` check fails with `000` when run on the machine -- while the deployment is perfectly reachable from outside. In-stack traffic is unaffected (containers reach the entry through an internal network alias). Either verify from your workstation, or point the two non-wildcard hosts at the local entry and re-run: ```bash echo "127.0.0.1 infra." | sudo tee -a /etc/hosts ./doctor.sh ``` ### S3 tools cannot connect through `infra.` The entry proxies **object paths only** (`//…` -- exactly what presigned upload/download URLs use, which is all the platform needs). The bucket-management API (list buckets, etc.) is not routed, so `mc alias set` and similar clients fail against the entry; point them at MinIO directly (SSH tunnel) for admin tasks.