From ded01ea2c659311cc3f5a2de39bd9ca40c2edb29 Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 18:28:04 +0800 Subject: [PATCH 01/12] docs(self-host): overhaul deploy section for the full-featured platform era - agent-runtime: rewritten from a 778-line inline deployment guide (stale compose/env, retired s3./git. domains, tagged agent image) into an architecture introduction; all deployable assets now link to teableio/teable-deployment as the single source of truth - new choose page: Teable Cloud vs standalone vs full-featured, first page of Quick Start (en+zh) - upgrade: rewritten for the real versioning model (date release tags, latest/beta channels, pinning; platform calver releases + CHANGELOG + doctor for full-featured); drops teable-ee image references and 0.5.0-alpha era examples - k8s page (raw standalone manifests) removed; Kubernetes path now goes through the full-featured Helm chart; redirects added - one-key page shelved (templates currently non-functional); redirect to choose - aws: removed internal-only SANDBOX_URL:7070 disclosure (en+zh) - retargeted all inbound links (docker, production-overview, activate, azure, sandbox-agent, ai-setting) and refreshed stale early-stage wording - production-overview: clarified scope (standalone scaling) with pointer to the full-featured platform Co-Authored-By: Claude Fable 5 --- docs.json | 26 +- en/basic/admin-panel/ai-setting.mdx | 6 +- en/basic/admin-panel/sandbox-agent.mdx | 2 +- en/deploy/activate.mdx | 3 +- en/deploy/agent-runtime.mdx | 880 ++++--------------------- en/deploy/aws.mdx | 2 - en/deploy/azure.mdx | 2 +- en/deploy/choose.mdx | 22 + en/deploy/docker.mdx | 2 +- en/deploy/k8s.mdx | 426 ------------ en/deploy/one-key.mdx | 41 -- en/deploy/production-overview.mdx | 11 +- en/deploy/upgrade.mdx | 159 ++--- zh/basic/admin-panel/ai-setting.mdx | 6 +- zh/basic/admin-panel/sandbox-agent.mdx | 2 +- zh/deploy/activate.mdx | 3 +- zh/deploy/agent-runtime.mdx | 841 +++-------------------- zh/deploy/aws.mdx | 2 - zh/deploy/azure.mdx | 2 +- zh/deploy/choose.mdx | 21 + zh/deploy/docker.mdx | 2 +- zh/deploy/k8s.mdx | 429 ------------ zh/deploy/one-key.mdx | 36 - zh/deploy/production-overview.mdx | 9 +- zh/deploy/upgrade.mdx | 178 ++--- 25 files changed, 434 insertions(+), 2679 deletions(-) create mode 100644 en/deploy/choose.mdx delete mode 100644 en/deploy/k8s.mdx delete mode 100644 en/deploy/one-key.mdx create mode 100644 zh/deploy/choose.mdx delete mode 100644 zh/deploy/k8s.mdx delete mode 100644 zh/deploy/one-key.mdx diff --git a/docs.json b/docs.json index 0a4e3539..8c5c8054 100644 --- a/docs.json +++ b/docs.json @@ -35,6 +35,22 @@ { "source": "/zh/about", "destination": "/zh/basic/ai/overview" + }, + { + "source": "/en/deploy/k8s", + "destination": "/en/deploy/agent-runtime" + }, + { + "source": "/zh/deploy/k8s", + "destination": "/zh/deploy/agent-runtime" + }, + { + "source": "/en/deploy/one-key", + "destination": "/en/deploy/choose" + }, + { + "source": "/zh/deploy/one-key", + "destination": "/zh/deploy/choose" } ], "navigation": { @@ -295,8 +311,8 @@ { "group": "Quick Start", "pages": [ - "en/deploy/docker", - "en/deploy/one-key" + "en/deploy/choose", + "en/deploy/docker" ] }, { @@ -320,7 +336,6 @@ "pages": [ "en/deploy/production-overview", "en/deploy/agent-runtime", - "en/deploy/k8s", "en/deploy/aws", "en/deploy/azure", "en/deploy/gcp" @@ -659,8 +674,8 @@ { "group": "快速开始", "pages": [ - "zh/deploy/docker", - "zh/deploy/one-key" + "zh/deploy/choose", + "zh/deploy/docker" ] }, { @@ -684,7 +699,6 @@ "pages": [ "zh/deploy/production-overview", "zh/deploy/agent-runtime", - "zh/deploy/k8s", "zh/deploy/aws", "zh/deploy/azure", "zh/deploy/gcp" diff --git a/en/basic/admin-panel/ai-setting.mdx b/en/basic/admin-panel/ai-setting.mdx index 0a1be64b..f61cdd0f 100644 --- a/en/basic/admin-panel/ai-setting.mdx +++ b/en/basic/admin-panel/ai-setting.mdx @@ -12,7 +12,7 @@ The **AI Settings** page is used to configure **AI Chat**, **AI fields**, **AI a ## Before you start -Before you open the AI Settings page, make sure the following items are ready. For self-hosted AI Chat and App Builder, first follow [Agent Runtime Deployment](/en/deploy/agent-runtime). +Before you open the AI Settings page, make sure the following items are ready. For self-hosted AI Chat and App Builder, first deploy the runtime plane — see [Agent Runtime Architecture](/en/deploy/agent-runtime). | Requirement | Required? | Notes | |------|------|------| @@ -77,7 +77,7 @@ To let users use the microphone in AI Chat, enable voice input and enter the **O ### Configure Agent Runtime and App Builder -Self-hosted **AI Chat** and **App Builder** require Agent Runtime setup and a compatible chat model. When you use your own key (BYOK), the chat model must come from an **OpenAI Compatible** or **Anthropic** provider. Self-hosted Agent Runtime is still in an early stage, and setup can be complex. Follow [Agent Runtime Deployment](/en/deploy/agent-runtime), which will continue to evolve based on deployment feedback. For assistance, contact **support@teable.ai**. +Self-hosted **AI Chat** and **App Builder** require Agent Runtime setup and a compatible chat model. When you use your own key (BYOK), the chat model must come from an **OpenAI Compatible** or **Anthropic** provider. Deploy the runtime plane first — see [Agent Runtime Architecture](/en/deploy/agent-runtime) and the [teable-deployment](https://github.com/teableio/teable-deployment) repository. For assistance, contact **support@teable.ai**. @@ -125,7 +125,7 @@ Check the **Pending configuration** panel first to see which item is still missi -Check whether the current deployment has completed [Agent Runtime Deployment](/en/deploy/agent-runtime), including the required Teable environment variables. For assistance, contact **support@teable.ai**. +Check whether the runtime plane is deployed and connected (see [Agent Runtime Architecture](/en/deploy/agent-runtime)), including the required Teable environment variables — the deployment's doctor script verifies the whole chain. For assistance, contact **support@teable.ai**. diff --git a/en/basic/admin-panel/sandbox-agent.mdx b/en/basic/admin-panel/sandbox-agent.mdx index 8359932a..4354c450 100644 --- a/en/basic/admin-panel/sandbox-agent.mdx +++ b/en/basic/admin-panel/sandbox-agent.mdx @@ -10,7 +10,7 @@ Path: Admin Panel → Sandbox Agent The **Sandbox Agent** page contains **Settings** and **Sandboxes** tabs for configuring how AI Chat runs in sandboxes. -For self-hosted setup, complete [Agent Runtime Deployment](/en/deploy/agent-runtime) before configuring this page. +For self-hosted setup, deploy the full-featured runtime plane first — see [Agent Runtime Architecture](/en/deploy/agent-runtime). ## Runtime Limits diff --git a/en/deploy/activate.mdx b/en/deploy/activate.mdx index 121eda19..f4faf230 100644 --- a/en/deploy/activate.mdx +++ b/en/deploy/activate.mdx @@ -25,8 +25,7 @@ Before subscribing to a plan, you need to have Teable installed and running on y If you haven't installed Teable yet, please follow one of these installation guides: - [Docker Deployment](/en/deploy/docker) (Recommended for quick setup) -- [Kubernetes Deployment](/en/deploy/k8s) (For production environments) -- [One-Click Cloud Deployment](/en/deploy/one-key) (Template-based setup) +- [Full-featured platform](/en/deploy/agent-runtime) (AI features and App Builder, Docker or Kubernetes) Make sure your Teable instance is up and running before proceeding to the next steps. diff --git a/en/deploy/agent-runtime.mdx b/en/deploy/agent-runtime.mdx index 48197157..ff08e224 100644 --- a/en/deploy/agent-runtime.mdx +++ b/en/deploy/agent-runtime.mdx @@ -1,778 +1,128 @@ --- -title: "Agent Runtime Deployment" -description: "Deploy a self-hosted Teable Agent Runtime stack on a Linux server with Docker Compose." +title: "Agent Runtime Architecture" +description: "How the self-hosted AI runtime plane works: sandboxes, App Builder, deployed apps, domains, and versioning." --- Available for self-hosted Business plan and above -Teable Agent Runtime provides the sandbox and app runtime services used by **AI Chat** and **App Builder**. This guide deploys the complete stack on a single Linux server with Docker Compose. +Teable's AI features — **AI Chat**, **App Builder**, and **app deployments** — +run on a dedicated **runtime plane** that you self-host next to the Teable app. +This page explains the architecture. The deployable assets (compose files, Helm +chart, values) live in +[teableio/teable-deployment](https://github.com/teableio/teable-deployment) and +are the single source of truth — nothing on this page needs to be copied. -The deployment includes the AI sandbox engine, App Runtime, git repository service, artifact storage, and TLS gateway. Kubernetes and an additional source repository are not required because every configuration file is included below. +## Two planes - -Self-hosted Agent Runtime is still in an early stage. This guide is intended for early adopters and will continue to evolve based on deployment feedback. The current runtime uses OpenSandbox through Teable Infra and does not use Vercel Sandbox or Vercel snapshots. For deployment assistance, contact **support@teable.ai**. - +A full-featured deployment is two planes working together: -Use the complete stack when you need App Builder previews and deployed apps. Running only `opensandbox-server` does not provide the app runtime, git registry, artifact storage, or gateway required by App Builder. - -## Architecture - -Only the Caddy gateway exposes ports 80 and 443 to the public network. All other services stay inside internal Docker networks. - -| Service | Description | Internal port | -|---|---|---| -| **caddy** | TLS termination and routing for the console, sandbox and app previews, S3, and git | Public 80/443 | -| **opensandbox-server** | AI sandbox engine in Docker mode. It uses the host `docker.sock` to create sandbox containers on demand. | 8090 | -| **infra-service** | Console UI, Infra API, App Runtime Docker backend, and built-in app gateway | 8080 | -| **git-registry** | Git smart-http service for App Builder. It uses the same image as `infra-service`. | 8080 | -| **minio** *(optional)* | Built-in S3-compatible artifact storage. Can be replaced with external S3. See [Choose Artifact Storage](#choose-artifact-storage). | 9000 / 9001 | -| **minio-init** *(optional)* | One-time task that waits for MinIO to become ready and creates the artifact bucket | N/A | - -Domain routes are derived from the root `BASE_DOMAIN`. - -| Address | Purpose | +| Plane | What runs there | |---|---| -| `https://` | Console UI, Infra API, and sandbox engine API under `/v1/*` | -| `https://{appId}.app.` | Entry point for deployed apps | -| `https://{sandboxId}-{port}.sandbox.` | Sandbox port preview | -| `https://s3.` | Artifact storage in built-in MinIO mode. Presigned URLs point here. | -| `https://git.` | Git smart-http endpoint, optional for manual external clone | - -## Before You Begin - -### Server Requirements - -| | Minimum | Recommended | -|---|---|---| -| vCPU | 4 | 8 | -| Memory | 8 GB | 16 GB | -| Disk | 80 GB NVMe | 120+ GB NVMe | -| OS | Ubuntu 22.04/24.04 (x86_64) | Ubuntu 22.04/24.04 (x86_64) | -| Software | Docker Engine 24+, Docker Compose v2.24+, OpenSSL | Docker Engine 24+, Docker Compose v2.24+, OpenSSL | - -Capacity notes: each active build session runs one sandbox container with a limit of 2 vCPU and 4 GiB. Each deployed app also has a limit of 2 vCPU and 4 GiB, though typical usage is only a few hundred MB. The base services use about 1 to 1.5 GiB of memory. Disk usage mainly comes from container images, about 15 to 20 GB after warm-up, plus writable layers for sandboxes and apps. Monitor free disk space. - -### DNS and Firewall - -Add **4 A records** at your DNS provider. All records must point to the server public IP. A wildcard certificate only covers one subdomain level, so all 4 records are required. - -```text - A -> -*. A -> -*.app. A -> -*.sandbox. A -> -``` - -If the domain is hosted on Cloudflare, set these 4 records to **DNS only (gray cloud)**, not **Proxied (orange cloud)**. - -Only allow inbound **22 (SSH), 80, and 443** in the firewall. Do not expose other ports. - -### Container Images - -This deployment uses **GHCR + Docker Hub** by default. **All images are public and do not require `docker login`**. For Teable images, `` is not a fixed value. Open each corresponding GHCR package page and use the release tag shown in that package page's Installation command. Confirm the tag for each image separately. Do not assume the tags are the same across images. - -#### Images Managed by Docker Compose - -These images are pulled and started by `docker compose up`. - -| Purpose | Image template | Package page | -|---|---|---| -| Console, Infra API, and App Runtime. `infra-service` and `git-registry` use the same image. The latter only uses a different startup entry point. | `ghcr.io/teableio/teable-infra-service:` | [GHCR](https://github.com/orgs/teableio/packages/container/package/teable-infra-service) | -| AI sandbox engine, OpenSandbox in Docker runtime mode, Teable build | `ghcr.io/teableio/opensandbox-server:` | [GHCR](https://github.com/orgs/teableio/packages/container/package/opensandbox-server) | -| TLS edge gateway. The official image is used for static certificate mode. Cloudflare mode requires a local build, see [Choose a TLS Mode](#choose-a-tls-mode). | `caddy:2.9.1` | [Docker Hub](https://hub.docker.com/_/caddy) | -| Built-in artifact storage, only used in built-in MinIO mode. Pinning by digest is recommended for production. | `minio/minio:latest` | [Docker Hub](https://hub.docker.com/r/minio/minio) | -| One-time bucket creation tool, only used in built-in MinIO mode | `minio/mc:latest` | [Docker Hub](https://hub.docker.com/r/minio/mc) | - -#### Runtime Images - -These images are not started by compose directly. The services use the host `docker.sock` to create containers from them on demand. They are public images and can be pulled anonymously by the Docker daemon. Pre-pulling them as described in [Pre-pull Runtime Images](#pre-pull-runtime-images) is recommended to avoid first-use latency when creating the first sandbox or deploying the first app. - -| Purpose | Image template | Package page | -|---|---|---| -| Sandbox base image. Each AI build session runs inside this image. | `ghcr.io/teableio/teable-sandbox-agent:` | [GHCR](https://github.com/orgs/teableio/packages/container/package/teable-sandbox-agent) | -| App runtime base image. Each deployed app container uses this image. Apps cannot be deployed if this is not configured. | `ghcr.io/teableio/teable-app-runtime:` | [GHCR](https://github.com/orgs/teableio/packages/container/package/teable-app-runtime) | -| Sandbox execution agent `execd`, injected by the engine into each sandbox container | `ghcr.io/teableio/opensandbox-execd:v1.0.19-fix-1064` | [GHCR](https://github.com/orgs/teableio/packages/container/package/opensandbox-execd) | -| Sandbox outbound network sidecar, egress in DNS mode, created by the engine for each sandbox as needed | `opensandbox/egress:v1.0.12` | [Docker Hub](https://hub.docker.com/r/opensandbox/egress) | - -Use the Teable-built fixed version of execd shown above, `opensandbox-execd:v1.0.19-fix-1064`. Do not replace it with the upstream `opensandbox/execd` image. - -### Choose Artifact Storage - -| Mode | Configuration | Notes | -|---|---|---| -| **Built-in MinIO** (default) | Leave `S3_ENDPOINT` empty in `.env`, keep `COMPOSE_PROFILES=minio` | MinIO starts automatically and the bucket is created. Presigned URLs use `https://s3.`. | -| **External S3** | Set `S3_ENDPOINT` to the provider URL and set `COMPOSE_PROFILES=` to empty | Built-in MinIO no longer starts. `S3_ACCESS_KEY`, `S3_SECRET_KEY`, and `S3_BUCKET` must use provider-issued values. The bucket must already exist. If the provider only supports virtual-host style, set `S3_FORCE_PATH_STYLE=false`. | - -## Create the Deployment Files - -### Deployment Directory - -Create a deployment directory on the server. This guide uses `teable-runtime/` as the example directory. The [Configuration Files](#configuration-files) section contains the full contents of the first four files. - -```text -teable-runtime/ -|-- .env # Edit for your environment -|-- compose.yaml # Service composition, copy as is -|-- Caddyfile # Gateway routing, adjust two places for the TLS mode -|-- opensandbox.toml # Sandbox engine configuration, copy as is -|-- compose.override.yaml # Generated during deployment, Git service signing key -|-- certs/ # Static certificate mode only -| |-- fullchain.pem -| `-- privkey.pem -`-- Dockerfile.caddy # Cloudflare TLS mode only -``` - -The files containing secrets are `.env` and `compose.override.yaml`. Do not share them externally or commit them to a repository. - -### Configuration Files - -#### `.env` - -```bash .env -# ============================================================================ -# Teable Agent Runtime environment configuration. -# This is the only file that needs environment-specific changes. -# Generate secrets with commands such as `openssl rand -hex 32`. -# ============================================================================ - -# === Domain and TLS === -# Deployment root domain. The `app.`, `sandbox.`, `s3.`, and `git.` subdomains are derived from it. -BASE_DOMAIN=teable.example.com -# Console access host. Usually the same as BASE_DOMAIN. Use another host only if needed. -INFRA_HOST=teable.example.com -# Only used in Cloudflare TLS mode. Leave empty in static mode. -ACME_EMAIL= -CLOUDFLARE_API_TOKEN= - -# === Secrets === -# Shared API key for engine auth, Infra API, console login, and internal git calls. -OPENSANDBOX_API_KEY= -# Console session signing secret. -SESSION_SECRET= -# Built-in MinIO: generated values become the MinIO root account. -# External S3: use the access key and secret key issued by the provider. -S3_ACCESS_KEY= -S3_SECRET_KEY= - -# === Images, all public, docker login is not required === -# Copy each Teable image from the Installation command on its own GHCR package page. -# infra-service and git-registry use the same image. -INFRA_SERVICE_IMAGE=ghcr.io/teableio/teable-infra-service: -# Sandbox engine. -OPENSANDBOX_SERVER_IMAGE=ghcr.io/teableio/opensandbox-server: -# Sandbox base image. Each AI build session runs inside this image. -SANDBOX_OPENSANDBOX_IMAGE=ghcr.io/teableio/teable-sandbox-agent: -# App runtime base image. Apps cannot be deployed if this is not configured. -APP_RUNTIME_DEFAULT_IMAGE=ghcr.io/teableio/teable-app-runtime: -# Allowlist of app image prefixes, comma-separated. Must cover the default image prefix above. -APP_RUNTIME_ALLOWED_IMAGE_PREFIXES=ghcr.io/teableio/ - -# TLS gateway image. Static mode uses the official image. -# Cloudflare mode uses the locally built teable-caddy-cloudflare:2.9.1 image. -# See the TLS instructions below. -CADDY_IMAGE=caddy:2.9.1 -# Third-party images default to :latest. Pinning by digest is recommended for production, for example: -# MINIO_IMAGE=minio/minio@sha256:... -# MINIO_MC_IMAGE=minio/mc@sha256:... - -# === Artifact storage, S3 / MinIO === -# "minio" enables built-in MinIO. Leave empty when using external S3. -COMPOSE_PROFILES=minio -# Empty means built-in MinIO, with presigned URLs served from https://s3.. -# Set this to an external S3-compatible endpoint URL, for example https://s3.your-provider.com, -# to use external storage. Also set COMPOSE_PROFILES above to empty and create the bucket -# on the provider side before deployment. -S3_ENDPOINT= -S3_BUCKET=teable-app-artifacts -S3_REGION=us-east-1 -# Built-in MinIO requires true. Set false only if the external S3 provider supports -# virtual-host style only. -S3_FORCE_PATH_STYLE=true - -# === Docker networks === -# Usually no change is needed. SANDBOX_DOCKER_NETWORK must match network_mode in opensandbox.toml. -APP_RUNTIME_DOCKER_NETWORK=teable-appnet -SANDBOX_DOCKER_NETWORK=teable-sandbox-net -``` - -#### `compose.yaml` - -Copy this file as is. All environment-specific values are loaded from `.env`. - -```yaml compose.yaml -# Teable Agent Runtime, single-node Docker stack. -name: teable-agent-runtime - -networks: - # Control-plane network. infra-service and the app containers it starts are attached - # to this network so the built-in gateway can reach apps by IP. - teable-appnet: - name: ${APP_RUNTIME_DOCKER_NETWORK:-teable-appnet} - driver: bridge - # Sandbox-only network. Sandboxes are created in this network and isolated from - # the control plane. opensandbox-server also joins it so it can reach sandboxes by IP. - teable-sandbox-net: - name: ${SANDBOX_DOCKER_NETWORK:-teable-sandbox-net} - driver: bridge - -volumes: - opensandbox-state: - infra-state: - git-registry-data: - minio-data: - caddy-data: # TLS certificates and ACME account. Must be persistent. - caddy-config: - # Agent shared workspace. external means it must be created manually first: - # docker volume create teable-agent-juicefs - teable-agent-juicefs: - external: true - -services: - # AI sandbox engine in Docker mode. - opensandbox-server: - image: ${OPENSANDBOX_SERVER_IMAGE:?set in .env} - container_name: opensandbox-server - restart: unless-stopped - networks: [teable-appnet, teable-sandbox-net] - volumes: - - /var/run/docker.sock:/var/run/docker.sock # Create sandboxes on the host. - - ./opensandbox.toml:/etc/opensandbox/config.toml:ro - - opensandbox-state:/data - environment: - SANDBOX_CONFIG_PATH: /etc/opensandbox/config.toml - # API key is injected through the environment. It is not written into toml. - OPENSANDBOX_SERVER_API_KEY: ${OPENSANDBOX_API_KEY:?set in .env} - - # Console UI, Infra API, App Runtime Docker backend, and built-in app gateway. - infra-service: - image: ${INFRA_SERVICE_IMAGE:?set in .env} - container_name: infra-service - restart: unless-stopped - networks: [teable-appnet] - volumes: - - /var/run/docker.sock:/var/run/docker.sock # Start and manage app containers. - - infra-state:/var/lib/teable-app-runtime - - teable-agent-juicefs:/mnt/juicefs - environment: - # --- Backend selection --- - APP_RUNTIME_BACKEND: docker - K8S_IN_CLUSTER: "false" - PORT: "8080" - # --- Auth and session, one shared key for the stack --- - AUTH_PROVIDER: api-key - SESSION_SECRET: ${SESSION_SECRET:?set in .env} - OPENSANDBOX_API_KEY: ${OPENSANDBOX_API_KEY:?set in .env} - INFRA_API_KEY: ${OPENSANDBOX_API_KEY} - # --- Sandbox engine --- - OPENSANDBOX_RUNTIME_URL: http://opensandbox-server:8090 - SANDBOX_OPENSANDBOX_IMAGE: ${SANDBOX_OPENSANDBOX_IMAGE:?set in .env} - OPENSANDBOX_PREVIEW_WILDCARD_DOMAIN: "*.sandbox.${BASE_DOMAIN}" - # --- App Runtime, Docker backend --- - APP_RUNTIME_DOCKER_SOCKET_PATH: /var/run/docker.sock - APP_RUNTIME_DOCKER_NETWORK: ${APP_RUNTIME_DOCKER_NETWORK:-teable-appnet} - APP_RUNTIME_STATE_DIR: /var/lib/teable-app-runtime - APP_RUNTIME_INGRESS_MODE: gateway - APP_RUNTIME_HOST_MATCH_MODE: suffix - APP_RUNTIME_DOMAIN: app.${BASE_DOMAIN:?set in .env} # Apps run at {appId}.app.. - APP_RUNTIME_PUBLIC_SCHEME: https - APP_RUNTIME_DEFAULT_IMAGE: ${APP_RUNTIME_DEFAULT_IMAGE:-} - APP_RUNTIME_ALLOWED_IMAGE_PREFIXES: ${APP_RUNTIME_ALLOWED_IMAGE_PREFIXES:-ghcr.io/teableio/} - APP_RUNTIME_ARTIFACT_PUBLIC_ORIGIN: http://infra-service:8080 # App containers fetch artifacts through the internal service name. - # --- Artifact storage, S3 / built-in MinIO --- - APP_RUNTIME_ARTIFACT_STORE_PROVIDER: s3 - APP_RUNTIME_ARTIFACT_S3_ENDPOINT: ${S3_ENDPOINT:-https://s3.${BASE_DOMAIN}} - APP_RUNTIME_ARTIFACT_BUCKET: ${S3_BUCKET:-teable-app-artifacts} - APP_RUNTIME_ARTIFACT_S3_REGION: ${S3_REGION:-us-east-1} - APP_RUNTIME_ARTIFACT_S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY:?set in .env} - APP_RUNTIME_ARTIFACT_S3_ACCESS_KEY_SECRET: ${S3_SECRET_KEY:?set in .env} - APP_RUNTIME_ARTIFACT_S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE:-true} - # --- Git registry, JWT key injected through compose.override.yaml --- - GIT_REGISTRY_ENABLED: "true" - GIT_REGISTRY_INTERNAL_URL: http://git-registry:8080 - GIT_REGISTRY_SANDBOX_URL: http://git-registry:8080 # Sandboxes clone through the internal network. - GIT_REGISTRY_PUBLIC_URL: https://git.${BASE_DOMAIN} # Only for external or manual clone. - # --- File browser and S3-compatible API --- - FILE_BROWSER_ENABLED: "true" - FILE_BROWSER_ROOT: /mnt/juicefs - S3_COMPAT_ENABLED: "true" - healthcheck: - test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8080/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] - interval: 30s - timeout: 5s - retries: 5 - start_period: 40s - depends_on: - opensandbox-server: - condition: service_started - minio-init: # Wait for the artifact bucket to be created. - condition: service_completed_successfully - required: false # Ignored when MinIO profile is disabled in external S3 mode. - - # Git smart-http service for App Builder. It uses the same image as infra-service, - # with a different startup entry point. - git-registry: - image: ${INFRA_SERVICE_IMAGE:?set in .env} - container_name: git-registry - restart: unless-stopped - command: ["node", ".output/git-registry/server.mjs"] - networks: [teable-appnet, teable-sandbox-net] # sandbox-net lets sandboxes clone directly through the internal network. - volumes: - - git-registry-data:/data - environment: - GIT_REGISTRY_PORT: "8080" - GIT_REGISTRY_DATA_DIR: /data - GIT_REGISTRY_INTERNAL_API_KEY: ${OPENSANDBOX_API_KEY:?set in .env} - # GIT_REGISTRY_JWT_PUBLIC_KEY is injected through compose.override.yaml. - healthcheck: - test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8080/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] - interval: 30s - timeout: 5s - retries: 5 - start_period: 20s - - # Built-in artifact storage, profile "minio". Disabled in external S3 mode. - minio: - profiles: ["minio"] - image: ${MINIO_IMAGE:-minio/minio:latest} - container_name: minio - restart: unless-stopped - networks: [teable-appnet] - command: server /data --console-address ":9001" - volumes: - - minio-data:/data - environment: - MINIO_ROOT_USER: ${S3_ACCESS_KEY:?set in .env} - MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY:?set in .env} - MINIO_REGION: ${S3_REGION:-us-east-1} - # One-time bucket creation task. infra-service uses it as a startup gate. - minio-init: - profiles: ["minio"] - image: ${MINIO_MC_IMAGE:-minio/mc:latest} - container_name: minio-init - restart: "no" - networks: [teable-appnet] - depends_on: - - minio - entrypoint: ["/bin/sh", "-c"] - command: - - > - until mc alias set local http://minio:9000 "$$S3_ACCESS_KEY" "$$S3_SECRET_KEY" >/dev/null 2>&1; do - echo 'waiting for minio...'; sleep 2; - done; - mc mb --ignore-existing "local/$$S3_BUCKET"; - echo "bucket $$S3_BUCKET ready"; - environment: - S3_ACCESS_KEY: ${S3_ACCESS_KEY:?set in .env} - S3_SECRET_KEY: ${S3_SECRET_KEY:?set in .env} - S3_BUCKET: ${S3_BUCKET:-teable-app-artifacts} - - # TLS edge and routing. This is the only service exposed to the public network. - caddy: - image: ${CADDY_IMAGE:-caddy:2.9.1} - container_name: caddy - restart: unless-stopped - networks: - teable-appnet: - aliases: - - s3.${BASE_DOMAIN} # Let infra-service reach Caddy by the presigned URL host inside the container network. - ports: - - "80:80" # ACME + http to https redirect - - "443:443" - - "443:443/udp" # HTTP/3 QUIC, optional - volumes: - - ./Caddyfile:/etc/caddy/Caddyfile:ro - - ./certs:/certs:ro # Certificate files for static TLS mode. Empty directory is harmless in Cloudflare mode. - - caddy-data:/data - - caddy-config:/config - environment: - BASE_DOMAIN: ${BASE_DOMAIN:?set in .env} - INFRA_HOST: ${INFRA_HOST:?set in .env} - ACME_EMAIL: ${ACME_EMAIL:-} # Cloudflare mode only - CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN:-} # Cloudflare mode only - OPENSANDBOX_API_KEY: ${OPENSANDBOX_API_KEY:?set in .env} # Server-side injection for sandbox preview requests - SANDBOX_PREVIEW_HOST: sandbox.${BASE_DOMAIN} - depends_on: - # Caddy is not gated on upstream health. It should start early for ACME. - # reverse_proxy retries automatically while upstream services are not ready. - opensandbox-server: - condition: service_started - infra-service: - condition: service_started - minio: - condition: service_started - required: false # MinIO is disabled in external S3 mode. - git-registry: - condition: service_started -``` - -#### `Caddyfile` - -The file is written for **static** certificate mode by default. For **cloudflare** mode, make the two changes described in the file header and under [Choose a TLS Mode](#choose-a-tls-mode). - -```caddyfile Caddyfile -# Teable Agent Runtime edge gateway. -# Default TLS mode: "static", using a self-provided wildcard certificate mounted at /certs. -# To switch to "cloudflare" mode, DNS-01 automatic issuance: -# 1. Uncomment the global block below. -# 2. Delete the `tls /certs/...` line in the (site_tls) snippet. - -# { -# email {$ACME_EMAIL} -# acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN} -# } - -# Site-level TLS for static mode. Leave this snippet empty in Cloudflare mode. -(site_tls) { - tls /certs/fullchain.pem /certs/privkey.pem -} - -# Console, Infra API, and sandbox endpoint preview rewrite. -# The engine endpoint preview API, /v1/sandboxes/{id}/endpoints/{port}, -# is rewritten to a root-path subdomain: -# {id}-{port}.sandbox., without scheme. The consumer adds the scheme. -(infra_and_v1) { - @endpoints path_regexp ep ^/v1/sandboxes/([a-z0-9-]+)/endpoints/([0-9]+)/?$ - handle @endpoints { - header Content-Type application/json - respond `{"endpoint":"{re.ep.1}-{re.ep.2}.{$SANDBOX_PREVIEW_HOST}"}` 200 - } - # Sandbox engine API, auth header is passed through. - handle /v1/* { - reverse_proxy opensandbox-server:8090 - } - # Console / Infra API. App preview also lands here and is routed by the built-in gateway by Host. - handle { - reverse_proxy infra-service:8080 - } -} - -# Sandbox preview. Each sandbox + port has a separate origin: -# Host = {id}-{port}.sandbox. -> engine server-proxy. -# The API key is injected server-side because browser top-level navigation cannot send custom headers. -# Accept-Encoding=identity disables compression to avoid multi-hop decoding issues. -# WebSocket is passed through by default. -(sandbox_preview) { - @sbx header_regexp sbx Host ^([a-z0-9-]+)-([0-9]+)\. - handle @sbx { - rewrite * /v1/sandboxes/{re.sbx.1}/proxy/{re.sbx.2}{uri} - reverse_proxy opensandbox-server:8090 { - header_up OPEN-SANDBOX-API-KEY {env.OPENSANDBOX_API_KEY} - header_up Accept-Encoding identity - } - } - handle { - respond 404 - } -} - -# ---- Sites ---- - -# Console, Infra API, and engine API. Teable's TEABLE_INFRA_API_URL points here. -{$INFRA_HOST} { - import site_tls - import infra_and_v1 -} - -# Deployed apps -*.app.{$BASE_DOMAIN} { - import site_tls - reverse_proxy infra-service:8080 -} - -# Sandbox preview -*.sandbox.{$BASE_DOMAIN} { - import site_tls - import sandbox_preview -} - -# Artifact storage. Used in built-in MinIO mode. The presigned URL host matches this site. -# In external S3 mode, this block is idle and harmless. -s3.{$BASE_DOMAIN} { - import site_tls - reverse_proxy minio:9000 -} - -# Git smart-http. Optional, only for external manual clone. -git.{$BASE_DOMAIN} { - import site_tls - reverse_proxy git-registry:8080 -} -``` - -#### `opensandbox.toml` - -Copy this file as is. - -```toml opensandbox.toml -# OpenSandbox engine configuration, Docker runtime mode. -# API key is not written in this file. It is injected by compose through -# OPENSANDBOX_SERVER_API_KEY. - -[server] -host = "0.0.0.0" -port = 8090 -api_key = "" # Injected from env. Leave empty. -max_sandbox_timeout_seconds = 86400 - -[runtime] -type = "docker" -# execd is the execution agent injected into each sandbox container. -# Use the Teable-built fixed image. Do not replace it with the upstream image. -execd_image = "ghcr.io/teableio/opensandbox-execd:v1.0.19-fix-1064" - -[store] -type = "sqlite" -path = "/data/opensandbox.db" # Stored in the opensandbox-state volume. - -[storage] -allowed_host_paths = [] # Bind-mount allowlist. Empty means allow all. Lock this down for production. -volume_default_size = "1Gi" - -[docker] -# Must match SANDBOX_DOCKER_NETWORK in .env, the sandbox network created by compose. -# The engine reaches sandboxes by container IP in this network. -network_mode = "teable-sandbox-net" -host_ip = "127.0.0.1" # Direct port mapping binds to loopback. Preview always goes through Caddy. -drop_capabilities = ["AUDIT_WRITE", "MKNOD", "NET_ADMIN", "NET_RAW", "SYS_ADMIN", "SYS_MODULE", "SYS_PTRACE", "SYS_TIME", "SYS_TTY_CONFIG"] -no_new_privileges = false # Allow setuid inside the sandbox. -pids_limit = 4096 # Fork-bomb protection. +| **App plane** | The Teable app itself, with its PostgreSQL and Redis | +| **Runtime plane** | Everything that powers the AI features (below) | -[ingress] -mode = "direct" # Docker runtime supports direct mode only. +The runtime plane consists of five services: -[egress] -# Sandbox outbound network sidecar. The engine creates it for each sandbox when needed. -image = "opensandbox/egress:v1.0.12" -mode = "dns" -``` - -## Deploy Agent Runtime - -### Configure `.env` and Generate Secrets - -Edit the `.env` file from [Configuration Files](#configuration-files). Required items: - -- `BASE_DOMAIN` / `INFRA_HOST`: your root domain. `INFRA_HOST` is usually the same as `BASE_DOMAIN`. -- Images: fill the image list under [Container Images](#container-images). For each Teable image, copy the `` from the Installation command on that image's GHCR package page. -- Secrets: generate and fill them with the commands below. - -```bash -openssl rand -hex 32 # -> OPENSANDBOX_API_KEY, one shared API key for the whole stack -openssl rand -hex 32 # -> SESSION_SECRET, console session signing secret -openssl rand -hex 12 # -> S3_ACCESS_KEY, generated for built-in MinIO, provider value for external S3 -openssl rand -hex 24 # -> S3_SECRET_KEY, same rule as above -``` - -### Choose a TLS Mode - -| Mode | Preparation | Certificate renewal | -|---|---|---| -| **static** (self-provided certificate, default) | Put `fullchain.pem` and `privkey.pem` into `certs/`. The certificate must be a single multi-SAN certificate covering ``, `*.`, `*.app.`, and `*.sandbox.`, and the private key must match it. Wildcards cover only one level. | Manual. Replace files in `certs/`, then run `docker compose exec caddy caddy reload -c /etc/caddy/Caddyfile`. | -| **cloudflare** (DNS-01 automatic issuance) | DNS must be hosted on Cloudflare. Prepare an API token with **Zone:Read + DNS:Edit** permissions. | Fully automatic through Caddy | - -**static mode**: the configuration files above are already written for this mode. Put the certificate files in place. No other change is needed. - -Use the following commands to check certificate SAN coverage and whether the certificate matches the private key: - -```bash -openssl x509 -in certs/fullchain.pem -noout -text | grep -A1 'Subject Alternative Name' -diff <(openssl x509 -in certs/fullchain.pem -noout -pubkey) \ - <(openssl pkey -in certs/privkey.pem -pubout) && echo "certificate and private key match" -``` - -**cloudflare mode** requires 4 changes: - -1. The official Caddy image does not include the DNS plugin. Save the following content as `Dockerfile.caddy` and build it locally: - - ```dockerfile Dockerfile.caddy - # Caddy with the Cloudflare DNS plugin. Wildcard certificates require DNS-01. - # Use caddy 2.9.x. The 2.8.x builder fails because of xcaddy dependency drift. - FROM caddy:2.9.1-builder AS builder - RUN xcaddy build --with github.com/caddy-dns/cloudflare - - FROM caddy:2.9.1 - COPY --from=builder /usr/bin/caddy /usr/bin/caddy - ``` - - ```bash - docker build -t teable-caddy-cloudflare:2.9.1 -f Dockerfile.caddy . - ``` - -2. In `.env`, set `CADDY_IMAGE=teable-caddy-cloudflare:2.9.1`, and fill `ACME_EMAIL` and `CLOUDFLARE_API_TOKEN`. -3. In `Caddyfile`, follow the file header and uncomment the global block, including `email` and `acme_dns cloudflare`. -4. In `Caddyfile`, delete the `tls /certs/...` line inside the `(site_tls)` snippet. Certificates will be managed automatically by ACME. - -In Cloudflare mode, the `caddy-data` volume must be persistent. It stores certificates and the ACME account. If it is lost, Caddy will request certificates again after rebuilds, which can trigger Let's Encrypt rate limits. - -### Generate the Git Service Signing Key - -The git service uses an Ed25519 key pair to sign and verify access tokens. Multi-line PEM values cannot be stored cleanly in `.env`, so they are injected through `compose.override.yaml`, which Docker Compose loads automatically. - -Run this in the deployment directory: - -```bash -openssl genpkey -algorithm ed25519 -out git-jwt.key -openssl pkey -in git-jwt.key -pubout -out git-jwt.pub -{ - echo "services:" - echo " infra-service:" - echo " environment:" - echo " GIT_REGISTRY_JWT_PRIVATE_KEY: |" - sed 's/^/ /' git-jwt.key - echo " git-registry:" - echo " environment:" - echo " GIT_REGISTRY_JWT_PUBLIC_KEY: |" - sed 's/^/ /' git-jwt.pub -} > compose.override.yaml -rm -f git-jwt.key git-jwt.pub -``` - -The generated `compose.override.yaml` will have this structure. The PEM content is your generated key: - -```yaml compose.override.yaml -services: - infra-service: - environment: - GIT_REGISTRY_JWT_PRIVATE_KEY: | - -----BEGIN PRIVATE KEY----- - ...your generated private key... - -----END PRIVATE KEY----- - git-registry: - environment: - GIT_REGISTRY_JWT_PUBLIC_KEY: | - -----BEGIN PUBLIC KEY----- - ...your generated public key... - -----END PUBLIC KEY----- -``` - -To rotate the key, run this section again. - -> If `openssl genpkey` reports `Algorithm ed25519 not found`, for example on macOS with LibreSSL, use Docker to generate the key pair, then run the same assembly command above: -> -> ```bash -> docker run --rm node:22-slim node -e 'const c=require("crypto");const{privateKey}=c.generateKeyPairSync("ed25519");process.stdout.write(privateKey.export({type:"pkcs8",format:"pem"}))' > git-jwt.key -> docker run --rm -v "$PWD:/w" node:22-slim node -e 'const c=require("crypto"),fs=require("fs");const k=c.createPrivateKey(fs.readFileSync("/w/git-jwt.key"));fs.writeFileSync("/w/git-jwt.pub",c.createPublicKey(k).export({type:"spki",format:"pem"}))' -> ``` - -### Create the Shared Workspace Volume - -The Agent shared workspace uses an external volume. Since compose declares it as `external`, compose will not create or delete it automatically. - -```bash -docker volume create teable-agent-juicefs -``` - -### Pre-pull Runtime Images - -All images are public. `docker login` is not required. - -```bash -set -a; . ./.env; set +a -docker pull "$OPENSANDBOX_SERVER_IMAGE" -docker pull "$SANDBOX_OPENSANDBOX_IMAGE" -docker pull "$APP_RUNTIME_DEFAULT_IMAGE" -docker pull ghcr.io/teableio/opensandbox-execd:v1.0.19-fix-1064 -docker pull opensandbox/egress:v1.0.12 -``` - -The execd and egress image addresses must exactly match the values configured in `opensandbox.toml`. - -### Start the Services - -```bash -docker compose up -d -docker compose ps # All services should be running / healthy. minio-init should be exited(0). - -# In cloudflare mode, watch certificate issuance on first startup. It usually takes seconds to about one minute. -docker compose logs -f caddy -``` - -## Verify the Deployment - -### Check Service Health - -```bash -KEY=$(grep '^OPENSANDBOX_API_KEY=' .env | cut -d= -f2) - -# Sandbox engine -curl -s -H "OPEN-SANDBOX-API-KEY: $KEY" https:///v1/sandboxes; echo -# Infra API -curl -s -o /dev/null -w "health -> %{http_code}\n" https:///api/health -``` - -Console UI: open `https://` in a browser, or the host configured in `INFRA_HOST`. Log in with `api-key`. The key is the `OPENSANDBOX_API_KEY` value in `.env`. - -### Connect Teable - -Add these environment variables to the Teable deployment: - -```bash -TEABLE_INFRA_API_URL=https:// -TEABLE_INFRA_API_KEY= -SANDBOX_OPENSANDBOX_IMAGE=ghcr.io/teableio/teable-sandbox-agent: -APP_DEPLOY_PROVIDER=docker-runtime -``` - -Use the same value for `TEABLE_INFRA_API_KEY` as `OPENSANDBOX_API_KEY` in the Agent Runtime `.env` file. The `SANDBOX_OPENSANDBOX_IMAGE` value must also match the sandbox image configured for Agent Runtime. - -`TEABLE_INFRA_API_URL` must point to the Teable Infra root URL without a path. Restart Teable after updating the variables. - -## Troubleshooting - -| Problem | What to check | +| Service | Purpose | |---|---| -| Teable reports that Agent Runtime is not configured | Confirm all four Teable environment variables above are set, then restart Teable. | -| Sandbox or app previews return DNS, TLS, or 404 errors | Confirm all four DNS records point to the server and the certificate covers every required hostname. | -| The first sandbox or app deployment is slow | Pre-pull all runtime images before the first use. | -| A service is unhealthy | Run `docker compose ps`, then inspect the affected service with `docker compose logs `. | - - -If you need deployment assistance, contact **support@teable.ai** and include the output of `docker compose ps` and the relevant service logs. - - -## Operations - -### View Logs and Manage Services - -```bash -docker compose logs -f infra-service # / opensandbox-server / caddy / git-registry -docker compose restart infra-service -docker compose down # Stop services and keep data volumes. -``` - -### Upgrade - -Update the image tags in `.env`, then run `docker compose pull && docker compose up -d`. - -After updating the sandbox or app base image, or execd / egress, pre-pull the images again as described in [Pre-pull Runtime Images](#pre-pull-runtime-images). - -### Back Up Data - -Back up these volumes regularly, especially `minio-data` and `caddy-data`. - -| Volume | Content | +| **Sandbox engine** | Runs every AI session inside an isolated sandbox container | +| **Infra Service** | Console + API the Teable app talks to; orchestrates builds and app deployments | +| **Git registry** | Source of truth for the apps you build (App Builder pushes here) | +| **Object storage** | Attachments and build artifacts (S3-compatible) | +| **Preview gateway** | Serves sandbox previews and deployed apps in the browser | + +```mermaid +graph LR + subgraph "App plane" + T["Teable app"] + end + subgraph "Runtime plane" + I["Infra Service"] + E["Sandbox engine"] + G["Git registry"] + S["Object storage"] + W["Preview gateway"] + end + U["Browser"] --> T + T -- "AI session" --> I --> E + E -- "build & push" --> G + E -- "artifacts" --> S + U -- "previews & deployed apps" --> W + W --> E + + style T fill:#0D9373,stroke:#0a7a5e,color:#fff +``` + +An AI chat session runs inside a sandbox; App Builder builds inside the same +sandbox, pushes source to the git registry, stores the build artifact in object +storage, and the deployed app is served through the gateway. + +## One domain, four DNS records + +Everything hangs off **one base domain** — typically a subdomain of yours, such +as `teable.example.com`: + +| Record | Serves | |---|---| -| `opensandbox-state` | Sandbox engine state, SQLite | -| `infra-state` | App Runtime state and drafts | -| `git-registry-data` | Git repository data | -| `minio-data` | Artifact files, built-in MinIO mode | -| `teable-agent-juicefs` | Agent shared workspace, external volume created during deployment | -| `caddy-data` / `caddy-config` | TLS certificates, ACME account, and Caddy configuration | - -`docker compose down -v` deletes data volumes too, including artifacts, Git repositories, and certificates. Use it carefully. - -## Security and Limitations - -### Security - -- The only files containing secrets are `.env` and `compose.override.yaml`. Do not commit them to a repository or share them externally. -- The whole stack uses one shared `OPENSANDBOX_API_KEY`. If you need separate permissions, replace the corresponding environment variables for each service separately. -- `docker.sock` is mounted into `opensandbox-server` and `infra-service`. This is required for the Docker backend and is equivalent to host root access. Deploy only on a dedicated and trusted host. -- The only public entry point is Caddy on ports 80 and 443. The MinIO console on 9001 is not exposed. Use an SSH tunnel if access is needed. -- Sandboxes share the host kernel. This is **not** a strong multi-tenant isolation boundary. For public multi-tenant scenarios, configure a stronger isolation runtime for the sandbox engine, such as gVisor or Kata. - -### Capability Boundaries - -**Available**: full sandbox engine functionality; the main app lifecycle, including create, activate, gateway access, delete, dashboard, logs, and S3 artifacts; git push and pull; console `api-key` login and Infra API; file browser and S3-compatible API. - -**Unavailable or limited** due to Docker mode tradeoffs: app `host-prefix` rename returns 501; `scale-to-zero` idle scaling is a no-op; engine key rotation and server rolling upgrade are not supported; Kubernetes observability pages in the console are empty, which is expected. +| `` | The Teable app | +| `infra.` | Infra console + API — git (`/git`) and object storage (bucket paths) ride this host as paths | +| `*.app.` | Apps you built and deployed | +| `*.sandbox.` | Sandbox previews in the browser | + +Each name is only a default; every hostname can be overridden individually +(see the values example in the deployment repository). + +## How the agent stays in sync with the app + +The sandbox agent image is configured as a **prefix without a tag**. The Teable +app appends **its own release tag** when launching AI sessions, so the agent +always matches the app version — and the app preheats that exact image through +the Infra Service, so the first session doesn't wait on a multi-gigabyte pull. +You never pull or pin the agent manually. + +## Versioning + +The runtime plane ships as **platform releases** (`v..`) of +the deployment repository: + +- A **tag** is a verified snapshot: `versions.yaml` pins every component image + (with digests), and the repository's `CHANGELOG.md` says what changed and + what, if anything, you must do. +- The repository's `main` is the rolling latest. +- The bundled **doctor** scripts compare what your deployment actually runs + against the release manifest and report one of three states: compatible, + upgrade the Teable app, or an unknown (unverified) combination. + +The Teable app has its own release line (date-based tags; `latest` is the +stable channel) — see [Version Upgrade](/en/deploy/upgrade). Platform releases +declare the app compatibility window; the doctor checks it for you. + +## Deploy it + +Both paths install the same platform and are covered end to end in the +deployment repository: + + + + Everything on one machine — first full deployment, `local` or `server` mode. + + + One umbrella chart on an existing cluster; only `global.baseDomain` is required. + + + +Related topics, all maintained in the deployment repository: + +- **Already running standalone Teable?** Your data stays in place — the runtime + plane attaches next to it: [migration guide](https://github.com/teableio/teable-deployment/blob/main/migration/2026-07-basic-to-full-featured.md) +- **Private / corporate CA**: sandboxes must trust the CA your entry serves — + [private-ca.md](https://github.com/teableio/teable-deployment/blob/main/helm/private-ca.md) +- **Sizing, versions and mirrors**: [VERSIONS.md](https://github.com/teableio/teable-deployment/blob/main/VERSIONS.md) · + [images/README.md](https://github.com/teableio/teable-deployment/blob/main/images/README.md) +- **When something fails**: run the doctor first, then + [TROUBLESHOOTING.md](https://github.com/teableio/teable-deployment/blob/main/TROUBLESHOOTING.md) + +After deploying, connect your Teable app to the runtime plane with +`TEABLE_INFRA_API_URL` / `TEABLE_INFRA_API_KEY` (the deployment guides cover +this), then configure limits in +[Admin Panel → Sandbox Agent](/en/basic/admin-panel/sandbox-agent). diff --git a/en/deploy/aws.mdx b/en/deploy/aws.mdx index b3dc5ae9..f0011692 100644 --- a/en/deploy/aws.mdx +++ b/en/deploy/aws.mdx @@ -211,8 +211,6 @@ BACKEND_STORAGE_PUBLIC_BUCKET=teable-public- BACKEND_STORAGE_PRIVATE_BUCKET=teable-private- STORAGE_PREFIX=https://teable-public-.s3.us-west-2.amazonaws.com -# Optional: Sandbox service (if deploying separately) -# SANDBOX_URL=http://teable-sandbox:7070 ``` diff --git a/en/deploy/azure.mdx b/en/deploy/azure.mdx index 07199b9e..8510dba1 100644 --- a/en/deploy/azure.mdx +++ b/en/deploy/azure.mdx @@ -816,5 +816,5 @@ az aks enable-addons \ - [Self-Hosted Overview](/en/deploy/production-overview) — Architecture, sizing, and scaling - [Environment Variables Reference](/en/deploy/env) - [Object Storage (S3-compatible)](/en/deploy/storage) -- [Kubernetes Deployment](/en/deploy/k8s) (generic K8s guide) +- [Full-featured platform](/en/deploy/agent-runtime) (AI features and App Builder) - [AWS Deployment](/en/deploy/aws) (for S3 setup if using cross-cloud storage) diff --git a/en/deploy/choose.mdx b/en/deploy/choose.mdx new file mode 100644 index 00000000..5f5eefac --- /dev/null +++ b/en/deploy/choose.mdx @@ -0,0 +1,22 @@ +--- +title: "Choose Your Path" +description: "Pick between Teable Cloud, standalone self-hosting, and the full-featured self-hosted platform." +--- + +Three ways to run Teable — pick by what you need: + +| | **Teable Cloud** | **Standalone self-host** | **Full-featured self-host** | +|---|---|---|---| +| Tables, collaboration, API, automation | ✅ | ✅ | ✅ | +| AI features (chat, agents) | ✅ | ❌ | ✅ | +| App Builder (build & deploy apps) | ✅ | ❌ | ✅ | +| Sandboxes / previews | ✅ | ❌ | ✅ | +| Runs on | [teable.ai](https://teable.ai) — nothing to deploy | one machine: app + PostgreSQL | one machine (Docker) or a Kubernetes cluster | +| Start here | [teable.ai](https://teable.ai) | [Docker Deployment](/en/deploy/docker) | [Agent Runtime Architecture](/en/deploy/agent-runtime) → [teableio/teable-deployment](https://github.com/teableio/teable-deployment) | + + +Standalone and full-featured are not a fork in the road: the full-featured +platform **attaches a runtime plane next to your existing standalone Teable** — +your data stays in place. Start standalone today, add AI later: +[migration guide](https://github.com/teableio/teable-deployment/blob/main/migration/2026-07-basic-to-full-featured.md). + diff --git a/en/deploy/docker.mdx b/en/deploy/docker.mdx index dd118b4c..b244c53b 100644 --- a/en/deploy/docker.mdx +++ b/en/deploy/docker.mdx @@ -4,7 +4,7 @@ title: "Docker Deployment" **Docker Compose is recommended for small teams and evaluation** (< 50 users). -For production deployments, first read the [Overview](/en/deploy/production-overview), then follow [Kubernetes](/en/deploy/k8s) or a cloud-specific guide. +For production deployments, first read the [Overview](/en/deploy/production-overview) or a cloud-specific guide. Need AI features or App Builder? See the [full-featured platform](/en/deploy/agent-runtime) instead. ## Docker Compose Deployment diff --git a/en/deploy/k8s.mdx b/en/deploy/k8s.mdx deleted file mode 100644 index de1c7ffc..00000000 --- a/en/deploy/k8s.mdx +++ /dev/null @@ -1,426 +0,0 @@ ---- -title: "Kubernetes Deployment" ---- - - -**Recommended for:** Production deployments with 50+ users - - - -For resource sizing recommendations (CPU, memory, replicas), see [Self-Hosted Overview → Resource Recommendations](/en/deploy/production-overview#resource-recommendations). - - -## Prerequisites -### Teable Images -- Application image: `ghcr.io/teableio/teable:latest` - -### Runtime Environment -- Running Kubernetes cluster - -### Required External Services -#### Database Service -- PostgreSQL database: postgres:15.4 (version > 12) - - PG image: `postgres:15.4` - -#### Cache Service -- Redis cache service: redis:7.2.4 (version > 5) - - Redis image: `redis:7.2.4` - -#### Object Storage Service -- MinIO object storage service: `minio/minio:RELEASE.2025-04-22T22-12-26Z` - -### Required Tools -- kubectl command-line tool - -## Dependency Component Configuration -### File Storage (MinIO) -File storage service must be accessible from the public internet (directly accessible by end users) - -Two buckets need to be created in advance for file storage: - -| Bucket | Environment Variable | Permission | -|--------|---------------------|------------| -| Public bucket | `BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub` | **Public read (anonymous access required)** | -| Private bucket | `BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt` | Private (default) | - - -The public bucket **must** have anonymous read access enabled. Without this, users cannot view shared images, avatars, and other public files. - - -#### Create Buckets Using MinIO Client (mc) - -Before deploying Teable, create the required buckets: - -```sh -# Set up MinIO client alias -mc alias set teable-minio https://minio.example.com YOUR_ACCESS_KEY YOUR_SECRET_KEY - -# Create public bucket and set anonymous read access -mc mb --ignore-existing teable-minio/teable-pub -mc anonymous set public teable-minio/teable-pub - -# Create private bucket -mc mb --ignore-existing teable-minio/teable-pvt - -# Verify buckets exist -mc ls teable-minio -# Expected output: -# [2024-01-01 00:00:00 UTC] 0B teable-pub/ -# [2024-01-01 00:00:00 UTC] 0B teable-pvt/ -``` - -#### Teable MinIO Environment Variables Overview - -```sh -# Fixed value -BACKEND_STORAGE_PROVIDER=minio -# Public bucket -BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub -# Private bucket -BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt -# Public endpoint, important! Must be accessible by end users -BACKEND_STORAGE_MINIO_ENDPOINT=minio.example.com -# Same as above but with protocol -STORAGE_PREFIX=https://minio.example.com -# Internal network endpoint (Kubernetes DNS format: ..svc.cluster.local) -# ⚠️ Replace with your actual namespace, e.g., minio.teable.svc.cluster.local -BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT=minio.teable.svc.cluster.local -# Public port, typically 443 or 9000 -BACKEND_STORAGE_MINIO_PORT=443 -# Internal network port, typically 80 or 9000 -BACKEND_STORAGE_MINIO_INTERNAL_PORT=80 -# Enable HTTPS, note: if Teable uses HTTPS, MinIO must also use HTTPS to avoid CORS issues -BACKEND_STORAGE_MINIO_USE_SSL="true" -# Admin account -BACKEND_STORAGE_MINIO_ACCESS_KEY=root -# Admin password -BACKEND_STORAGE_MINIO_SECRET_KEY=rootPassword -``` - -### Database -Create a database account with administrative privileges, a database, and set a password. -Environment variables example: - - Database name: teable - - Password: your-password - - Username: postgres - - Port: 5432 -```sh -PRISMA_DATABASE_URL="postgresql://postgres:your-password@your-postgres-host:5432/teable" -``` - -### Redis Cache -Teable only needs the internal network address for Redis cache configuration. (Note: Redis manages both cache and queues, it's essential. Data should be backed up regularly) -Environment variables example: -```sh -BACKEND_CACHE_REDIS_URI="redis://username:password@your-redis-host:6379/0" -``` - -## Create Configuration Files - -```yaml teable-config.yaml (Non-sensitive configuration) -apiVersion: v1 -kind: ConfigMap -metadata: - name: teable-config - namespace: teable # Replace with your namespace -data: - # Application base configuration, public access domain - PUBLIC_ORIGIN: "https://your-domain.com" - - # Storage configuration - BACKEND_STORAGE_PROVIDER: "minio" - # Bucket names (must be created in MinIO before deployment) - BACKEND_STORAGE_PUBLIC_BUCKET: "teable-pub" - BACKEND_STORAGE_PRIVATE_BUCKET: "teable-pvt" - # Public endpoint, important! Must be accessible by end users - BACKEND_STORAGE_MINIO_ENDPOINT: "minio.example.com" - # Same as above but with protocol - STORAGE_PREFIX: "https://minio.example.com" - # Internal endpoint (Kubernetes DNS format: ..svc.cluster.local) - # ⚠️ Replace 'teable' with your actual namespace - BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT: "minio.teable.svc.cluster.local" - # Public port, typically 443 or 9000 - BACKEND_STORAGE_MINIO_PORT: "443" - # Internal port, typically 80 or 9000 - BACKEND_STORAGE_MINIO_INTERNAL_PORT: "80" - # Enable HTTPS, note: if Teable uses HTTPS, MinIO must also use HTTPS to avoid CORS issues - BACKEND_STORAGE_MINIO_USE_SSL: "true" - - # Cache configuration, fixed value - BACKEND_CACHE_PROVIDER: "redis" - - # Other configurations, fixed values - NEXT_ENV_IMAGES_ALL_REMOTE: "true" - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - # Keep this when using self-signed certificates - NODE_TLS_REJECT_UNAUTHORIZED: '0' -``` - -```yaml teable-secrets.yaml (Sensitive information) -apiVersion: v1 -kind: Secret -metadata: - name: teable-secrets - namespace: teable # Replace with your namespace -type: Opaque -stringData: - # Database sensitive information - PRISMA_DATABASE_URL: "postgresql://postgres:your-password@your-postgres-host:5432/teable" - - # Application secrets - BACKEND_JWT_SECRET: "your-jwt-secret" - BACKEND_SESSION_SECRET: "your-session-secret" - - # MinIO authentication - BACKEND_STORAGE_MINIO_ACCESS_KEY: "your-minio-access-key" - BACKEND_STORAGE_MINIO_SECRET_KEY: "your-minio-secret-key" - - # Redis authentication - BACKEND_CACHE_REDIS_URI: "redis://username:password@your-redis-host:6379/0" -``` - -```yaml teable-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: teable - namespace: teable # Replace with your namespace -spec: - replicas: 1 # Configure as needed - selector: - matchLabels: - app: teable - template: - metadata: - labels: - app: teable - spec: - # Add initContainers for database migration - initContainers: - - name: db-migrate - image: ghcr.io/teableio/teable:latest - args: - - migrate-only - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - requests: - cpu: 100m - memory: 102Mi - limits: - cpu: 1000m - memory: 1024Mi - containers: - - name: teable - image: ghcr.io/teableio/teable:latest - args: - - skip-migrate - ports: - - containerPort: 3000 - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - requests: - cpu: 200m - memory: 400Mi - limits: - cpu: 2000m - memory: 4096Mi - startupProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 10 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 30 - successThreshold: 1 - livenessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 30 - periodSeconds: 30 - timeoutSeconds: 5 - failureThreshold: 3 - successThreshold: 1 - readinessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 15 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 3 - successThreshold: 1 -``` - -```yaml teable-service.yaml -apiVersion: v1 -kind: Service -metadata: - name: teable - namespace: teable # Replace with your namespace -spec: - ports: - - port: 3000 - targetPort: 3000 - selector: - app: teable -``` - -```yaml teable-ingress.yaml (Optional) -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: teable - namespace: teable # Replace with your namespace - # Example using nginx, if using other ingress class, please replace - annotations: - nginx.ingress.kubernetes.io/proxy-body-size: "100m" - nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" - nginx.ingress.kubernetes.io/proxy-send-timeout: "600" - nginx.ingress.kubernetes.io/proxy-read-timeout: "600" - nginx.ingress.kubernetes.io/proxy-request-buffering: "off" -spec: - ingressClassName: nginx # Change to your ingress class if different - rules: - - host: your-domain.com # Replace with your domain - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: teable - port: - number: 3000 - tls: - - hosts: - - your-domain.com # Replace with your domain - secretName: your-tls-secret -``` - - -Do **NOT** add `nginx.ingress.kubernetes.io/rewrite-target` annotation. This will break Teable's internal routing. - - - -## Deployment Steps - - -Before deploying Teable, ensure that: -1. MinIO buckets (`teable-pub` and `teable-pvt`) are created -2. Public bucket has anonymous read access enabled -3. PostgreSQL database `teable` exists -4. Redis is accessible - - -1. Create namespace (if not exists): -```sh -kubectl create namespace teable -``` - -2. Create configuration and secrets: -```sh -kubectl apply -f teable-config.yaml -kubectl apply -f teable-secrets.yaml -``` - -3. Deploy application: -```sh -kubectl apply -f teable-deployment.yaml -kubectl apply -f teable-service.yaml -kubectl apply -f teable-ingress.yaml # Optional, if using Ingress -``` - -4. Verify deployment: -```sh -# Check Pod status -kubectl get pods -n teable -l app=teable - -# Wait for pods to be ready -kubectl rollout status deployment/teable -n teable - -# View application logs -kubectl logs -n teable -l app=teable -``` - -### Configuration Notes - -1. Sensitive Information Management - - All password, secret-related information should be managed through Secrets - -2. Database Configuration - - Ensure that the teable database exists in the database - - The database user needs to have appropriate permissions - - It's recommended to use a connection pool to manage database connections - -3. MinIO Configuration - - Ensure that the storage bucket exists and the permissions are correct, one public bucket, one private bucket - - The public bucket needs to be completely publicly readable - - The internal and external access address configuration is correct - -4. Redis Configuration - - It's recommended to enable Redis persistence - - Configure appropriate memory limits - - Consider using Redis cluster to improve availability - -5. Security Recommendations - - Use strong passwords and secrets - - Enable TLS/SSL encryption - - Regularly update certificates - - Limit network access scope - -6. Resource Configuration - - Adjust resource limits based on actual load - - Monitor resource usage - - Configure appropriate health check parameters - -### Troubleshooting - -#### Common Issues - -| Error | Cause | Solution | -|-------|-------|----------| -| `ECONNREFUSED` to MinIO | MinIO internal endpoint incorrect or unreachable | Verify `BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT` uses correct format: `..svc.cluster.local` | -| `Bucket not found` | MinIO buckets not created | Create buckets using `mc mb` command before deployment | -| `Access Denied` on file upload | MinIO credentials incorrect | Verify `BACKEND_STORAGE_MINIO_ACCESS_KEY` and `SECRET_KEY` | -| `Page not found` on web access | Ingress `rewrite-target` configured | Remove `rewrite-target` annotation from Ingress | -| Pod stuck in `CrashLoopBackOff` | Backend failed to start | Check logs for MinIO, database, or Redis connectivity issues | - -#### Diagnostic Commands - -1. Check Pod status: -```sh -kubectl describe pod -n teable -l app=teable -``` - -2. View application logs: -```sh -kubectl logs -n teable -l app=teable -kubectl logs -n teable -l app=teable --previous # If pod crashed -``` - -3. Verify configuration: -```sh -kubectl describe configmap teable-config -n teable -kubectl describe secret teable-secrets -n teable -``` - -4. Check network connection: -```sh -kubectl exec -it -n teable -- curl -v localhost:3000/health -``` - -5. Test MinIO connectivity from inside the cluster: -```sh -kubectl run test-minio --rm -it --image=curlimages/curl --restart=Never -n teable -- \ - curl -v http://minio.teable.svc.cluster.local/minio/health/live -``` diff --git a/en/deploy/one-key.mdx b/en/deploy/one-key.mdx deleted file mode 100644 index bf38c110..00000000 --- a/en/deploy/one-key.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: "One-Click Cloud Deployment" -description: "Deploy Teable quickly using cloud provider templates, then subscribe to unlock paid features and activate your license." ---- - -## One-Click Cloud Deployment - -One-click templates help you bootstrap a Teable environment quickly. After deployment, if you want paid features (for self-hosted plans), you should **subscribe** and **activate your license** using your Instance ID. - -## Step 1: Deploy with a template - -Choose a template below and deploy. - -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/NtH5uD?referralCode=rE4BjB) - -[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/QF8695) - -[![Deploy to RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploylobe.svg)](https://repocloud.io/details/?app_id=273) - -[![Deploy on Elestio](https://elest.io/images/logos/deploy-to-elestio-btn.png)](https://elest.io/open-source/teable) - -## Step 2: Subscribe (paid features) - -If you are deploying **self-hosted** and need paid features, subscribe with your **Instance ID**: - -1. Open the Admin Panel in your deployed instance and copy the **Instance ID** -2. Go to the self-hosted pricing page and subscribe: `https://app.teable.ai/public/pricing?host=self-hosted` -3. After subscribing, you will get a **License Key** - -Then activate it in your instance: - -- Documentation: [Subscribe and Activate License](/en/deploy/activate) - - -If you are using **Teable Cloud** (hosted by Teable), manage your plan in-app: -see [Billing & Subscription](/en/basic/space/billing). - - -## Need help? - -If you have questions about subscription or licensing, contact `support@teable.ai`. diff --git a/en/deploy/production-overview.mdx b/en/deploy/production-overview.mdx index c9e0269c..49f68366 100644 --- a/en/deploy/production-overview.mdx +++ b/en/deploy/production-overview.mdx @@ -7,6 +7,13 @@ description: "Architecture, sizing, and deployment path selection for production **Recommended for:** Production deployments with 50+ users + +This page covers scaling the **standalone** Teable app. If you need AI +features or App Builder, deploy the +[full-featured platform](/en/deploy/agent-runtime) instead — it includes the +app plus the AI runtime plane. + + ## Architecture Overview Teable uses a **stateless application tier** with externalized state services. This design enables horizontal scaling and high availability. @@ -149,8 +156,8 @@ The "Minimum" setup is for **evaluation only**. It has no redundancy—data loss ## Next Steps: Choose Your Platform - - Deploy on any K8s cluster with full control over infrastructure. + + The app plus the AI runtime plane on any K8s cluster, via one Helm chart. ECS + RDS + ElastiCache + S3. Recommended for 50+ users. diff --git a/en/deploy/upgrade.mdx b/en/deploy/upgrade.mdx index f9200055..71ed94c3 100644 --- a/en/deploy/upgrade.mdx +++ b/en/deploy/upgrade.mdx @@ -2,135 +2,106 @@ title: "Version Upgrade" --- -This document describes how to upgrade your self-hosted Teable deployment. +This document describes how to upgrade a self-hosted Teable deployment. -## Update Frequency +## Release channels and version tags -Teable releases updates on an irregular schedule, often at a high frequency. We may ship multiple updates in a week, so the release frequency could be very high. You have full control over when to update your self-hosted Teable instance. You can choose to upgrade weekly or monthly. Whenever a new version is released, you can flexibly decide whether to upgrade. You can view all available versions on [GitHub Packages](https://github.com/teableio/teable-enterprise/pkgs/container/teable-ee). +The Teable app publishes **date-based release tags** in the form +`release..` (for example +`release.2026-07-14T12-24-39Z.2228`), plus two floating channels: + +| Tag | Meaning | +|---|---| +| `latest` | The **stable** channel — always points at the most recent stable release | +| `beta` | The rolling channel — the newest build, ahead of stable | +| `release..` | An immutable, specific release | + +Releases ship frequently (often several per week); you decide when to upgrade. +All tags are listed on +[GitHub Packages](https://github.com/teableio/teable/pkgs/container/teable). Before performing any upgrade, we strongly recommend backing up your data first. -## Docker Compose Upgrade - -If you deployed Teable using Docker Compose, follow these steps to upgrade: - -### Backup Data (Recommended) - -We recommend backing up your entire cloud server, or backing up the PostgreSQL / Redis / storage volumes you're using. - -### Navigate to Deployment Directory - -```bash -cd teable # Navigate to your teable deployment directory -``` - -### Pull Latest Images +## Standalone: Docker Compose upgrade -```bash -docker-compose pull -``` - -### Restart Services +If you run standalone Teable with Docker Compose: ```bash -docker-compose up -d +cd teable # your deployment directory +docker compose pull +docker compose up -d ``` -The system will automatically recreate containers using the latest images. Your data is stored in Docker volumes and will not be lost. +Containers are recreated on the new image; your data lives in Docker volumes +(or your external database) and is not touched. -### Verify Upgrade +Verify: ```bash -# Check container status -docker-compose ps - -# View logs to confirm successful startup -docker-compose logs -f teable +docker compose ps +docker compose logs -f teable ``` -Visit your Teable instance to confirm the service is running properly. +### Pin a specific version -## Upgrade to Specific Version - -If you want to upgrade to a specific version instead of the latest, modify the image tag in `docker-compose.yaml`: +To run a specific release instead of the `latest` channel, set the image tag in +`docker-compose.yaml`: ```yaml services: teable: - image: ghcr.io/teableio/teable-ee:0.5.0-alpha-1.x.x-build.xxx # Specify version + image: ghcr.io/teableio/teable:release.2026-07-14T12-24-39Z.2228 ``` -Then execute: +then `docker compose pull && docker compose up -d`. Pinning is recommended for +production: you choose exactly when versions change. -```bash -docker-compose pull -docker-compose up -d -``` +## Full-featured deployments (AI runtime plane) - -You can view all available versions on the [GitHub Releases](https://github.com/teableio/teable-enterprise/pkgs/container/teable-ee) page. - +A full-featured deployment (see +[Agent Runtime Architecture](/en/deploy/agent-runtime)) has **two version +lines**: -## Kubernetes Upgrade +- **The Teable app** — the date-based tags above; upgrade it as described in + the deployment repository's guides. +- **The platform** — the + [teableio/teable-deployment](https://github.com/teableio/teable-deployment) + repository ships **platform releases** (`v..`): each git + tag is a verified snapshot of every runtime component, with + [`CHANGELOG.md`](https://github.com/teableio/teable-deployment/blob/main/CHANGELOG.md) + entries stating what changed and what you must do (most releases are + hot-swappable). Upgrade **from the repository checked out at the release + tag**, following + [`VERSIONS.md`](https://github.com/teableio/teable-deployment/blob/main/VERSIONS.md). -If you deployed Teable using Kubernetes, follow these steps to upgrade: +After upgrading either line, run the bundled **doctor** — it checks health and +compares what is actually running against the platform release manifest +(compatible / upgrade the Teable app / unknown combination). -### Manual Image Update +## Database migration -If you're using custom Kubernetes configuration, you can directly update the image version in your Deployment: +Teable executes database migrations automatically on startup; no manual step +is required. If anything looks wrong after an upgrade, check the logs: ```bash -kubectl set image deployment/teable teable=ghcr.io/teableio/teable-ee:0.5.0-alpha-1.x.x-build.xxx -n teable -``` - -### Verify Upgrade - -```bash -# Check Pod status -kubectl get pods -n teable - -# Check rollout status -kubectl rollout status deployment/teable -n teable - -# View logs -kubectl logs -f deployment/teable -n teable -``` - -## Database Migration - -Teable automatically executes database migrations on startup, no manual intervention required. If you encounter issues after upgrading, check the logs to confirm migration was successful: - -```bash -# Docker Compose -docker-compose logs teable | grep -i migration - -# Kubernetes -kubectl logs deployment/teable -n teable | grep -i migration +docker compose logs teable | grep -i migration ``` ## Rollback -If you encounter issues after upgrading, you can rollback to the previous version. - -### Docker Compose Rollback - -1. Modify the image tag in `docker-compose.yaml` to the previous version -2. Execute `docker-compose up -d` +If you hit issues after upgrading: -### Kubernetes Rollback +1. Set the image tag in `docker-compose.yaml` back to the previous release tag + (this is why pinning beats `latest`: the previous version is written down). +2. `docker compose up -d` -```bash -# View rollout history -kubectl rollout history deployment/teable -n teable - -# Rollback to previous version -kubectl rollout undo deployment/teable -n teable - -# Rollback to specific revision -kubectl rollout undo deployment/teable -n teable --to-revision= -``` + +Rolling back **after** a release that migrated the database schema may not be +safe — restore from your pre-upgrade backup in that case. This is the main +reason for the backup recommendation above. + ## FAQ @@ -147,18 +118,18 @@ Typically, pulling new images takes a few minutes (depending on network speed), -Using `docker-compose up -d` or Kubernetes rolling update, there will be a brief service interruption (usually a few seconds to tens of seconds). For zero-downtime upgrades, we recommend using Kubernetes with appropriate rolling update strategies configured. +Using `docker compose up -d` there will be a brief service interruption (usually a few seconds to tens of seconds). You can check the current version by: - Viewing the version number at the bottom left of the Teable interface - Using an admin account to access the admin panel -- Running `docker inspect teable-teable-1 --format='{{.Config.Image}}'` to check the image version +- Running `docker inspect --format='{{.Config.Image}}'` to check the image version. If you run the `latest` channel, the full-featured deployment ships a `pin-image.sh` helper that resolves which release `latest` currently is. -1. First check container logs to troubleshoot: `docker-compose logs teable` +1. First check container logs to troubleshoot: `docker compose logs teable` 2. If it's a database migration issue, try restoring from backup 3. If the issue persists, rollback to the previous version 4. Contact support at support@teable.ai diff --git a/zh/basic/admin-panel/ai-setting.mdx b/zh/basic/admin-panel/ai-setting.mdx index 47223cc7..d8a32153 100644 --- a/zh/basic/admin-panel/ai-setting.mdx +++ b/zh/basic/admin-panel/ai-setting.mdx @@ -12,7 +12,7 @@ description: "为私有化部署实例配置 AI 对话、AI 字段、AI 自动 ## 开始前的准备 -在进入 AI 配置页之前,先确认下面这些准备项已经就绪。私有化部署如需使用 AI 对话和应用构建器,请先按照[部署 Agent Runtime](/zh/deploy/agent-runtime)完成部署。 +在进入 AI 配置页之前,先确认下面这些准备项已经就绪。私有化部署如需使用 AI 对话和应用构建器,请先部署运行平面,见 [Agent Runtime 架构](/zh/deploy/agent-runtime)。 | 准备项 | 是否必需 | 说明 | |------|------|------| @@ -77,7 +77,7 @@ description: "为私有化部署实例配置 AI 对话、AI 字段、AI 自动 ### 配置 Agent Runtime 与应用构建器 -私有化部署如需使用 **AI 对话** 和 **应用构建器**,需要先完成 Agent Runtime 配置,并配置兼容的聊天模型。使用自带 Key 时,聊天模型必须来自 **OpenAI Compatible**(OpenAI 兼容)或 **Anthropic** 类型的提供商。Agent Runtime 的私有化部署仍处于早期阶段,配置流程较繁琐。请按照[部署 Agent Runtime](/zh/deploy/agent-runtime)部署所需服务,该文档会根据实际部署反馈持续迭代。如需协助,请联系 **support@teable.ai**。 +私有化部署如需使用 **AI 对话** 和 **应用构建器**,需要先完成 Agent Runtime 配置,并配置兼容的聊天模型。使用自带 Key 时,聊天模型必须来自 **OpenAI Compatible**(OpenAI 兼容)或 **Anthropic** 类型的提供商。请先部署运行平面 —— 见 [Agent Runtime 架构](/zh/deploy/agent-runtime) 与 [teable-deployment](https://github.com/teableio/teable-deployment) 仓库。如需协助,请联系 **support@teable.ai**。 @@ -125,7 +125,7 @@ description: "为私有化部署实例配置 AI 对话、AI 字段、AI 自动 -请检查当前部署是否已经按照[部署 Agent Runtime](/zh/deploy/agent-runtime)完成部署,并配置所需的 Teable 环境变量。如需协助,请联系 **support@teable.ai**。 +请检查运行平面是否已部署并接通(见 [Agent Runtime 架构](/zh/deploy/agent-runtime)),以及所需的 Teable 环境变量是否配置 —— 部署仓库自带的 doctor 脚本可以整链验证。如需协助,请联系 **support@teable.ai**。 diff --git a/zh/basic/admin-panel/sandbox-agent.mdx b/zh/basic/admin-panel/sandbox-agent.mdx index 776890ef..8f9827a1 100644 --- a/zh/basic/admin-panel/sandbox-agent.mdx +++ b/zh/basic/admin-panel/sandbox-agent.mdx @@ -10,7 +10,7 @@ description: "配置和管理沙箱 Agent。" **沙箱 Agent** 页面包含 **设置** 和 **沙箱** 两个页签,用于配置 AI 对话的沙箱运行方式。 -私有化部署请先完成[部署 Agent Runtime](/zh/deploy/agent-runtime),再配置此页面。 +私有化部署请先部署全功能运行平面,见 [Agent Runtime 架构](/zh/deploy/agent-runtime)。 ## 运行限制 diff --git a/zh/deploy/activate.mdx b/zh/deploy/activate.mdx index 50d8e395..d942392e 100644 --- a/zh/deploy/activate.mdx +++ b/zh/deploy/activate.mdx @@ -25,8 +25,7 @@ Teable 为自托管部署提供了不同的订阅计划,每个计划都有其 如果您还未安装 Teable,请按照以下安装指南之一进行操作: - [Docker 部署](/zh/deploy/docker)(推荐快速安装) -- [Kubernetes 部署](/zh/deploy/k8s)(适用于生产环境) -- [一键部署](/zh/deploy/one-key)(简化安装流程) +- [全功能平台](/zh/deploy/agent-runtime)(AI 能力与 App Builder,Docker 或 Kubernetes) 在进行下一步之前,请确保您的 Teable 实例已成功启动并运行。 diff --git a/zh/deploy/agent-runtime.mdx b/zh/deploy/agent-runtime.mdx index 1294f215..ed19bcdb 100644 --- a/zh/deploy/agent-runtime.mdx +++ b/zh/deploy/agent-runtime.mdx @@ -1,773 +1,116 @@ --- -title: "部署 Agent Runtime" -description: "使用 Docker Compose 在 Linux 服务器上部署私有化 Teable Agent Runtime。" +title: "Agent Runtime 架构" +description: "自部署 AI 运行面的工作原理:沙箱、App Builder、应用部署、域名模型与版本体系。" --- -私有化部署商业版及以上适用 +私有化部署 Business 及以上套餐可用 -Teable Agent Runtime 为 **AI 对话** 和 **应用构建器** 提供沙箱及应用运行时服务。本指南介绍如何使用 Docker Compose,在一台 Linux 服务器上部署完整服务栈。 +Teable 的 AI 能力 —— **AI Chat**、**App Builder** 与**应用部署** —— 运行在一个与 +Teable 应用并列自部署的**运行面**上。本页只讲架构;可部署资产(compose、Helm chart、 +values)全部在 [teableio/teable-deployment](https://github.com/teableio/teable-deployment) +仓库中维护,那里是唯一事实源 —— 本页没有任何需要复制的内容。 -部署内容包括 AI 沙箱引擎、App Runtime、Git 仓库服务、制品存储和 TLS 网关。本文提供全部配置文件,无需 Kubernetes 或额外的代码仓库。 +## 两个平面 - -Agent Runtime 的私有化部署仍处于早期阶段。本指南面向愿意率先体验的用户,并会根据实际部署反馈持续迭代。当前运行时通过 Teable Infra 使用 OpenSandbox,不再使用 Vercel Sandbox 或 Vercel Snapshot。如需部署协助,请联系 **support@teable.ai**。 - +一套全功能部署由两个平面组成: -如需使用应用构建器预览和发布应用,请部署本文提供的完整服务栈。只运行 `opensandbox-server` 无法提供应用构建器所需的应用运行时、Git 仓库、制品存储和网关。 - -## 架构 - -对外只暴露 Caddy 网关的 80/443 端口,其余服务均在内部 Docker 网络中: - -| 服务 | 说明 | 内部端口 | -|---|---|---| -| **caddy** | TLS 终止 + 全部路由(控制台、沙箱/应用预览、S3、git) | 对外 80/443 | -| **opensandbox-server** | AI 沙箱引擎(Docker 模式),通过宿主 `docker.sock` 按需创建沙箱容器 | 8090 | -| **infra-service** | 控制台 UI + Infra API + App Runtime Docker 后端 + 内置应用网关 | 8080 | -| **git-registry** | App Builder 的 git smart-http 服务(与 infra-service 共用镜像) | 8080 | -| **minio** *(可选)* | 内置 S3 兼容制品存储(可换成外部 S3,请参阅[选择制品存储](#选择制品存储)) | 9000 / 9001 | -| **minio-init** *(可选)* | 一次性任务:等待 MinIO 就绪并创建制品桶 | 不适用 | - -域名路由(均由根域名 `BASE_DOMAIN` 自动拼出): - -| 地址 | 用途 | +| 平面 | 内容 | |---|---| -| `https://` | 控制台 UI + Infra API + 沙箱引擎 API(`/v1/*`) | -| `https://{appId}.app.` | 已部署应用的访问入口 | -| `https://{sandboxId}-{port}.sandbox.` | 沙箱端口预览 | -| `https://s3.` | 制品存储(内置 MinIO 模式;预签名 URL 指向这里) | -| `https://git.` | git smart-http(外部手动 clone 用,可选) | - -## 开始前的准备 - -### 服务器要求 - -| | 最低 | 推荐 | -|---|---|---| -| vCPU | 4 | 8 | -| 内存 | 8 GB | 16 GB | -| 磁盘 | 80 GB NVMe | 120+ GB NVMe | -| 操作系统 | Ubuntu 22.04/24.04(x86_64) | 同左 | -| 软件 | Docker Engine 24+、Docker Compose v2.24+、OpenSSL | 同左 | - -容量背景:每个活跃的构建会话运行一个沙箱容器(上限 2 vCPU / 4 GiB);每个已部署应用同样上限 -2 vCPU / 4 GiB(实际常用几百 MB)。基础服务共占约 1 到 1.5 GiB 内存。磁盘大头是容器镜像 -(预热后约 15 到 20 GB)加沙箱/应用的可写层,建议监控剩余空间。 - -### DNS 与防火墙 - -在 DNS 供应商处添加 **4 条 A 记录**,全部指向云主机公网 IP(通配证书只覆盖一级子域名, -所以 4 条都需要): - -``` - A → <公网IP> -*. A → <公网IP> -*.app. A → <公网IP> -*.sandbox. A → <公网IP> -``` - -域名托管在 Cloudflare 时,这 4 条记录请设置为**仅 DNS(灰云)**,不要开代理(橙云)。 - -防火墙只放行入站 **22(SSH)、80、443**,其余端口一律不对外。 - -### 容器镜像 - -本部署默认使用 **GHCR + Docker Hub**。**所有镜像均为公开仓库,无需 -`docker login`**。Teable 镜像中的 `` 不是固定值;请分别打开对应的 GHCR Package 页面, -使用该页面 Installation 命令中显示的版本标签。不同镜像的标签需要分别确认,不要假设相同。 - -#### Docker Compose 管理的镜像 - -| 用途 | 镜像模板 | Package page | -|---|---|---| -| 控制台 + Infra API + App Runtime(**infra-service 与 git-registry 共用**,后者只是换了启动入口) | `ghcr.io/teableio/teable-infra-service:` | [GHCR](https://github.com/orgs/teableio/packages/container/package/teable-infra-service) | -| AI 沙箱引擎本体(OpenSandbox,Docker 运行时模式;Teable 构建版) | `ghcr.io/teableio/opensandbox-server:` | [GHCR](https://github.com/orgs/teableio/packages/container/package/opensandbox-server) | -| TLS 边缘网关(自备证书模式用官方镜像;Cloudflare 模式本地构建,请参阅[选择 TLS 模式](#选择-tls-模式)) | `caddy:2.9.1` | [Docker Hub](https://hub.docker.com/_/caddy) | -| 内置制品存储(仅内置 MinIO 模式;生产建议钉 digest) | `minio/minio:latest` | [Docker Hub](https://hub.docker.com/r/minio/minio) | -| 一次性建桶工具(仅内置 MinIO 模式) | `minio/mc:latest` | [Docker Hub](https://hub.docker.com/r/minio/mc) | - -#### 运行时镜像 - -以下镜像不由 compose 启动,而是由服务在运行期通过宿主 `docker.sock` 按需创建容器时使用。 -均为公开镜像,守护进程可直接匿名拉取;建议按照[预拉取运行时镜像](#预拉取运行时镜像)预拉到本地,避免首次创建沙箱/部署应用时的 -冷启动拉取延迟: - -| 用途 | 镜像模板 | Package page | -|---|---|---| -| 沙箱基准镜像(每个 AI 构建会话在其中运行) | `ghcr.io/teableio/teable-sandbox-agent:` | [GHCR](https://github.com/orgs/teableio/packages/container/package/teable-sandbox-agent) | -| 应用运行基准镜像(每个已部署应用的容器镜像;**不配置则应用无法部署**) | `ghcr.io/teableio/teable-app-runtime:` | [GHCR](https://github.com/orgs/teableio/packages/container/package/teable-app-runtime) | -| 沙箱内执行代理 execd(引擎自动注入进每个沙箱容器) | `ghcr.io/teableio/opensandbox-execd:v1.0.19-fix-1064` | [GHCR](https://github.com/orgs/teableio/packages/container/package/opensandbox-execd) | -| 沙箱出网 sidecar egress(DNS 模式,引擎按需为每个沙箱创建) | `opensandbox/egress:v1.0.12` | [Docker Hub](https://hub.docker.com/r/opensandbox/egress) | - -execd 必须使用上表中由 Teable 构建的修复版镜像 `opensandbox-execd:v1.0.19-fix-1064`,不要替换为上游的 `opensandbox/execd` 镜像。 - -### 选择制品存储 - -| 模式 | 配置 | 说明 | -|---|---|---| -| **内置 MinIO**(默认) | `.env` 中 `S3_ENDPOINT` 留空、`COMPOSE_PROFILES=minio` | 自动启动 MinIO 并建桶;预签名 URL 统一走 `https://s3.`。 | -| **外部 S3** | `S3_ENDPOINT` 填供应商 URL、`COMPOSE_PROFILES=`(置空) | 内置 MinIO 不再启动;`S3_ACCESS_KEY` / `S3_SECRET_KEY` / `S3_BUCKET` 须填供应商签发的值,**桶要预建**;供应商若仅支持 virtual-host style 另填 `S3_FORCE_PATH_STYLE=false`。 | - -## 创建部署文件 - -### 部署目录 - -在服务器上创建一个部署目录(下文以 `teable-runtime/` 为例),[配置文件](#配置文件)部分提供前四个文件的完整内容: - -``` -teable-runtime/ -├── .env # 根据部署环境修改 -├── compose.yaml # 服务编排,原样复制 -├── Caddyfile # 网关路由,按 TLS 模式调整两处 -├── opensandbox.toml # 沙箱引擎配置,原样复制 -├── compose.override.yaml # 部署时生成,Git 服务签名密钥 -├── certs/ # 仅自备证书(static)模式 -│ ├── fullchain.pem -│ └── privkey.pem -└── Dockerfile.caddy # 仅 Cloudflare TLS 模式 -``` - -含密钥的文件是 `.env` 与 `compose.override.yaml`,请勿外发、勿提交到版本库。 - -### 配置文件 - -#### `.env` - -```bash .env -# ============================================================================ -# Teable Agent Runtime 环境配置,唯一需要按部署环境修改的文件。 -# ============================================================================ - -# === 域名与 TLS === -# 部署根域名,app.、sandbox.、s3. 和 git. 子域名由此生成。 -BASE_DOMAIN=teable.example.com -# 控制台访问 Host,通常与 BASE_DOMAIN 相同,仅在需要单独域名时修改。 -INFRA_HOST=teable.example.com -# 仅 cloudflare TLS 模式使用(static 模式留空): -ACME_EMAIL= -CLOUDFLARE_API_TOKEN= - -# === 密钥 === -# 整栈共用的一把 API Key(引擎鉴权 / Infra API / 控制台登录 / git 内部)。 -OPENSANDBOX_API_KEY= -# 控制台会话签名密钥。 -SESSION_SECRET= -# 内置 MinIO:生成的值将作为 MinIO root 账号。 -# 外部 S3:填写服务商签发的 access key 和 secret key。 -S3_ACCESS_KEY= -S3_SECRET_KEY= - -# === 镜像,均为公开仓库,无需 docker login === -# Teable 镜像的 请分别从对应 GHCR package 页面的 Installation 命令复制。 -# infra-service 与 git-registry 共用此镜像。 -INFRA_SERVICE_IMAGE=ghcr.io/teableio/teable-infra-service: -# 沙箱引擎。 -OPENSANDBOX_SERVER_IMAGE=ghcr.io/teableio/opensandbox-server: -# 沙箱基准镜像(每个 AI 构建会话在其中运行)。 -SANDBOX_OPENSANDBOX_IMAGE=ghcr.io/teableio/teable-sandbox-agent: -# 应用运行基准镜像(不配置则应用无法部署)。 -APP_RUNTIME_DEFAULT_IMAGE=ghcr.io/teableio/teable-app-runtime: -# 允许部署的应用镜像前缀白名单,多个前缀以逗号分隔,必须包含上方默认镜像的前缀。 -APP_RUNTIME_ALLOWED_IMAGE_PREFIXES=ghcr.io/teableio/ - -# TLS 网关镜像。static 模式使用官方镜像;Cloudflare 模式使用本地构建的 teable-caddy-cloudflare:2.9.1。 -CADDY_IMAGE=caddy:2.9.1 -# 第三方镜像默认使用 :latest。生产环境建议固定 digest,例如: -# MINIO_IMAGE=minio/minio@sha256:... -# MINIO_MC_IMAGE=minio/mc@sha256:... - -# === 制品存储(S3 / MinIO) === -# "minio" 表示启用内置 MinIO。改用外部 S3 时置空。 -COMPOSE_PROFILES=minio -# 留空 = 内置 MinIO(预签名走 https://s3.)。 -# 填写外部 S3 兼容服务 URL(如 https://s3.your-provider.com)即可改用外部存储。 -# 同时将 COMPOSE_PROFILES 置空,并在服务商侧预先创建存储桶。 -S3_ENDPOINT= -S3_BUCKET=teable-app-artifacts -S3_REGION=us-east-1 -# 内置 MinIO 必须设置为 true。外部 S3 仅支持 virtual-host style 时设置为 false。 -S3_FORCE_PATH_STYLE=true - -# === Docker 网络(一般不改;SANDBOX_DOCKER_NETWORK 须与 opensandbox.toml -# 的 network_mode 一致) === -APP_RUNTIME_DOCKER_NETWORK=teable-appnet -SANDBOX_DOCKER_NETWORK=teable-sandbox-net -``` - -#### `compose.yaml` - -原样复制,无需修改(所有环境相关的值都取自 `.env`): - -```yaml -# Teable Agent Runtime 单机 Docker 服务栈。 -name: teable-agent-runtime - -networks: - # 控制面网:infra-service 与它拉起的应用容器同挂,内置网关才能按 IP 直达应用。 - teable-appnet: - name: ${APP_RUNTIME_DOCKER_NETWORK:-teable-appnet} - driver: bridge - # 沙箱专用网:沙箱起在此网,与控制面隔离;opensandbox-server 同接以按容器 IP 直达。 - teable-sandbox-net: - name: ${SANDBOX_DOCKER_NETWORK:-teable-sandbox-net} - driver: bridge - -volumes: - opensandbox-state: - infra-state: - git-registry-data: - minio-data: - caddy-data: # TLS 证书 + ACME 账号,务必持久化 - caddy-config: - # Agent 共享工作区。external:先手动创建一次 - # docker volume create teable-agent-juicefs - teable-agent-juicefs: - external: true - -services: - # AI 沙箱引擎(Docker 模式)。 - opensandbox-server: - image: ${OPENSANDBOX_SERVER_IMAGE:?set in .env} - container_name: opensandbox-server - restart: unless-stopped - networks: [teable-appnet, teable-sandbox-net] - volumes: - - /var/run/docker.sock:/var/run/docker.sock # 在宿主上创建沙箱 - - ./opensandbox.toml:/etc/opensandbox/config.toml:ro - - opensandbox-state:/data - environment: - SANDBOX_CONFIG_PATH: /etc/opensandbox/config.toml - # API key 经环境变量注入(不写进 toml)。 - OPENSANDBOX_SERVER_API_KEY: ${OPENSANDBOX_API_KEY:?set in .env} - - # 控制台 + Infra API + App Runtime Docker 后端 + 内置应用网关。 - infra-service: - image: ${INFRA_SERVICE_IMAGE:?set in .env} - container_name: infra-service - restart: unless-stopped - networks: [teable-appnet] - volumes: - - /var/run/docker.sock:/var/run/docker.sock # 起/管应用容器 - - infra-state:/var/lib/teable-app-runtime - - teable-agent-juicefs:/mnt/juicefs - environment: - # --- 后端选择 --- - APP_RUNTIME_BACKEND: docker - K8S_IN_CLUSTER: "false" - PORT: "8080" - # --- 鉴权 / 会话(整栈共用一把 key) --- - AUTH_PROVIDER: api-key - SESSION_SECRET: ${SESSION_SECRET:?set in .env} - OPENSANDBOX_API_KEY: ${OPENSANDBOX_API_KEY:?set in .env} - INFRA_API_KEY: ${OPENSANDBOX_API_KEY} - # --- 沙箱引擎 --- - OPENSANDBOX_RUNTIME_URL: http://opensandbox-server:8090 - SANDBOX_OPENSANDBOX_IMAGE: ${SANDBOX_OPENSANDBOX_IMAGE:?set in .env} - OPENSANDBOX_PREVIEW_WILDCARD_DOMAIN: "*.sandbox.${BASE_DOMAIN}" - # --- App Runtime(Docker 后端) --- - APP_RUNTIME_DOCKER_SOCKET_PATH: /var/run/docker.sock - APP_RUNTIME_DOCKER_NETWORK: ${APP_RUNTIME_DOCKER_NETWORK:-teable-appnet} - APP_RUNTIME_STATE_DIR: /var/lib/teable-app-runtime - APP_RUNTIME_INGRESS_MODE: gateway - APP_RUNTIME_HOST_MATCH_MODE: suffix - APP_RUNTIME_DOMAIN: app.${BASE_DOMAIN:?set in .env} # 应用跑在 {appId}.app.<域名> - APP_RUNTIME_PUBLIC_SCHEME: https - APP_RUNTIME_DEFAULT_IMAGE: ${APP_RUNTIME_DEFAULT_IMAGE:-} - APP_RUNTIME_ALLOWED_IMAGE_PREFIXES: ${APP_RUNTIME_ALLOWED_IMAGE_PREFIXES:-ghcr.io/teableio/} - APP_RUNTIME_ARTIFACT_PUBLIC_ORIGIN: http://infra-service:8080 # 应用容器经内网服务名回源拉制品 - # --- 制品存储(S3 / 内置 MinIO) --- - APP_RUNTIME_ARTIFACT_STORE_PROVIDER: s3 - APP_RUNTIME_ARTIFACT_S3_ENDPOINT: ${S3_ENDPOINT:-https://s3.${BASE_DOMAIN}} - APP_RUNTIME_ARTIFACT_BUCKET: ${S3_BUCKET:-teable-app-artifacts} - APP_RUNTIME_ARTIFACT_S3_REGION: ${S3_REGION:-us-east-1} - APP_RUNTIME_ARTIFACT_S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY:?set in .env} - APP_RUNTIME_ARTIFACT_S3_ACCESS_KEY_SECRET: ${S3_SECRET_KEY:?set in .env} - APP_RUNTIME_ARTIFACT_S3_FORCE_PATH_STYLE: ${S3_FORCE_PATH_STYLE:-true} - # --- git registry(JWT 密钥经 compose.override.yaml 注入) --- - GIT_REGISTRY_ENABLED: "true" - GIT_REGISTRY_INTERNAL_URL: http://git-registry:8080 - GIT_REGISTRY_SANDBOX_URL: http://git-registry:8080 # 沙箱走内网 clone - GIT_REGISTRY_PUBLIC_URL: https://git.${BASE_DOMAIN} # 仅外部/手动 clone - # --- 文件页 / S3 兼容接口 --- - FILE_BROWSER_ENABLED: "true" - FILE_BROWSER_ROOT: /mnt/juicefs - S3_COMPAT_ENABLED: "true" - healthcheck: - test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8080/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] - interval: 30s - timeout: 5s - retries: 5 - start_period: 40s - depends_on: - opensandbox-server: - condition: service_started - minio-init: # 等建好制品桶再启动 - condition: service_completed_successfully - required: false # 外部 S3 模式(minio profile 停用)时忽略 - - # App Builder 的 git smart-http(与 infra-service 同镜像,换启动入口)。 - git-registry: - image: ${INFRA_SERVICE_IMAGE:?set in .env} - container_name: git-registry - restart: unless-stopped - command: ["node", ".output/git-registry/server.mjs"] - networks: [teable-appnet, teable-sandbox-net] # sandbox-net:沙箱直接内网 clone - volumes: - - git-registry-data:/data - environment: - GIT_REGISTRY_PORT: "8080" - GIT_REGISTRY_DATA_DIR: /data - GIT_REGISTRY_INTERNAL_API_KEY: ${OPENSANDBOX_API_KEY:?set in .env} - # GIT_REGISTRY_JWT_PUBLIC_KEY 经 compose.override.yaml 注入 - healthcheck: - test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8080/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] - interval: 30s - timeout: 5s - retries: 5 - start_period: 20s - - # 内置制品存储(profile "minio";外部 S3 模式停用)。 - minio: - profiles: ["minio"] - image: ${MINIO_IMAGE:-minio/minio:latest} - container_name: minio - restart: unless-stopped - networks: [teable-appnet] - command: server /data --console-address ":9001" - volumes: - - minio-data:/data - environment: - MINIO_ROOT_USER: ${S3_ACCESS_KEY:?set in .env} - MINIO_ROOT_PASSWORD: ${S3_SECRET_KEY:?set in .env} - MINIO_REGION: ${S3_REGION:-us-east-1} - # 一次性建桶;infra-service 以它作启动门控。 - minio-init: - profiles: ["minio"] - image: ${MINIO_MC_IMAGE:-minio/mc:latest} - container_name: minio-init - restart: "no" - networks: [teable-appnet] - depends_on: - - minio - entrypoint: ["/bin/sh", "-c"] - command: - - > - until mc alias set local http://minio:9000 "$$S3_ACCESS_KEY" "$$S3_SECRET_KEY" >/dev/null 2>&1; do - echo 'waiting for minio...'; sleep 2; - done; - mc mb --ignore-existing "local/$$S3_BUCKET"; - echo "bucket $$S3_BUCKET ready"; - environment: - S3_ACCESS_KEY: ${S3_ACCESS_KEY:?set in .env} - S3_SECRET_KEY: ${S3_SECRET_KEY:?set in .env} - S3_BUCKET: ${S3_BUCKET:-teable-app-artifacts} - -# TLS 边缘网关和路由,唯一对公网开放的服务。 - caddy: - image: ${CADDY_IMAGE:-caddy:2.9.1} - container_name: caddy - restart: unless-stopped - networks: - teable-appnet: - aliases: - - s3.${BASE_DOMAIN} # 让 infra-service 在容器网内以预签名 host 直达 caddy - ports: - - "80:80" # ACME + http->https 跳转 - - "443:443" - - "443:443/udp" # HTTP/3(QUIC),可选 - volumes: - - ./Caddyfile:/etc/caddy/Caddyfile:ro - - ./certs:/certs:ro # static TLS 模式的证书;cloudflare 模式空目录无害 - - caddy-data:/data - - caddy-config:/config - environment: - BASE_DOMAIN: ${BASE_DOMAIN:?set in .env} - INFRA_HOST: ${INFRA_HOST:?set in .env} - ACME_EMAIL: ${ACME_EMAIL:-} # 仅 cloudflare 模式 - CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN:-} # 仅 cloudflare 模式 - OPENSANDBOX_API_KEY: ${OPENSANDBOX_API_KEY:?set in .env} # 沙箱预览请求服务端注入 - SANDBOX_PREVIEW_HOST: sandbox.${BASE_DOMAIN} - depends_on: - # caddy 不门控在上游健康上:它要先起来跑 ACME,上游未就绪时 reverse_proxy 会自动重试。 - opensandbox-server: - condition: service_started - infra-service: - condition: service_started - minio: - condition: service_started - required: false # 外部 S3 模式 minio 停用 - git-registry: - condition: service_started -``` - -#### `Caddyfile` - -默认按 **static**(自备证书)模式写好;**cloudflare** 模式按照文件顶部注释和[选择 TLS 模式](#选择-tls-模式)调整两处: - -```caddyfile Caddyfile -# Teable Agent Runtime 边缘网关。 -# 默认 TLS 模式 "static"(自备通配证书挂载在 /certs)。 -# 改用 "cloudflare"(DNS-01 自动签发)模式时: -# 1. 取消下面全局块的注释; -# 2. 删除 (site_tls) 片段里的 `tls /certs/...` 一行。 - -# { -# email {$ACME_EMAIL} -# acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN} -# } - -# 站点级 TLS(static 模式)。cloudflare 模式此片段留空。 -(site_tls) { - tls /certs/fullchain.pem /certs/privkey.pem -} - -# 控制台 + Infra API + 沙箱端口预览改写。 -# 引擎的端口预览接口(/v1/sandboxes/{id}/endpoints/{port})被改写为根路径子域名 -# {id}-{port}.sandbox.<域名>(不带 scheme,由消费方自行补全)。 -(infra_and_v1) { - @endpoints path_regexp ep ^/v1/sandboxes/([a-z0-9-]+)/endpoints/([0-9]+)/?$ - handle @endpoints { - header Content-Type application/json - respond `{"endpoint":"{re.ep.1}-{re.ep.2}.{$SANDBOX_PREVIEW_HOST}"}` 200 - } - # 沙箱引擎 API(鉴权头透传) - handle /v1/* { - reverse_proxy opensandbox-server:8090 - } - # 控制台 / Infra API;应用预览也落这里(内置网关按 Host 命中) - handle { - reverse_proxy infra-service:8080 - } -} - -# 沙箱预览(每个 沙箱+端口 是独立 origin): -# Host = {id}-{port}.sandbox.<域名> -> 引擎 server-proxy。 -# API key 服务端注入(浏览器顶层导航带不了请求头); -# Accept-Encoding=identity 关压缩避免多跳解码错;WebSocket 默认透传。 -(sandbox_preview) { - @sbx header_regexp sbx Host ^([a-z0-9-]+)-([0-9]+)\. - handle @sbx { - rewrite * /v1/sandboxes/{re.sbx.1}/proxy/{re.sbx.2}{uri} - reverse_proxy opensandbox-server:8090 { - header_up OPEN-SANDBOX-API-KEY {env.OPENSANDBOX_API_KEY} - header_up Accept-Encoding identity - } - } - handle { - respond 404 - } -} - -# ---- 站点 ---- - -# 控制台 + Infra API + 引擎 API(Teable 的 TEABLE_INFRA_API_URL 指向这里) -{$INFRA_HOST} { - import site_tls - import infra_and_v1 -} - -# 已部署应用 -*.app.{$BASE_DOMAIN} { - import site_tls - reverse_proxy infra-service:8080 -} - -# 沙箱预览 -*.sandbox.{$BASE_DOMAIN} { - import site_tls - import sandbox_preview -} - -# 制品存储(内置 MinIO 模式;预签名 URL 的 host 与此站点一致。 -# 外部 S3 模式下此块闲置,无害。) -s3.{$BASE_DOMAIN} { - import site_tls - reverse_proxy minio:9000 -} - -# git smart-http(可选,外部手动 clone 用) -git.{$BASE_DOMAIN} { - import site_tls - reverse_proxy git-registry:8080 -} -``` - -#### `opensandbox.toml` +| **应用平面** | Teable 应用本体,及其 PostgreSQL 与 Redis | +| **运行平面** | 支撑全部 AI 能力的服务(见下) | -原样复制: +运行平面包含五个服务: -```toml opensandbox.toml -# OpenSandbox 引擎配置(Docker 运行时模式)。 -# API key 不写入此文件,由 Compose 环境变量 OPENSANDBOX_SERVER_API_KEY 注入。 - -[server] -host = "0.0.0.0" -port = 8090 -api_key = "" # 经 env 注入,留空即可 -max_sandbox_timeout_seconds = 86400 - -[runtime] -type = "docker" -# execd:注入进每个沙箱容器的执行代理。使用 Teable 构建的修复版,勿换上游官方镜像。 -execd_image = "ghcr.io/teableio/opensandbox-execd:v1.0.19-fix-1064" - -[store] -type = "sqlite" -path = "/data/opensandbox.db" # 落在 opensandbox-state 卷 - -[storage] -allowed_host_paths = [] # bind-mount 白名单(空 = 全放;生产建议锁定) -volume_default_size = "1Gi" - -[docker] -# 须与 .env 的 SANDBOX_DOCKER_NETWORK 一致(compose 创建的沙箱网); -# 引擎经此网按容器 IP 直达沙箱。 -network_mode = "teable-sandbox-net" -host_ip = "127.0.0.1" # direct 端口映射绑回环;预览统一走 Caddy -drop_capabilities = ["AUDIT_WRITE", "MKNOD", "NET_ADMIN", "NET_RAW", "SYS_ADMIN", "SYS_MODULE", "SYS_PTRACE", "SYS_TIME", "SYS_TTY_CONFIG"] -no_new_privileges = false # 允许沙箱内 setuid -pids_limit = 4096 # 防 fork 炸弹 - -[ingress] -mode = "direct" # Docker 运行时仅支持 direct - -[egress] -# 沙箱出网 sidecar,引擎按需为每个沙箱创建。 -image = "opensandbox/egress:v1.0.12" -mode = "dns" -``` - -## 部署 Agent Runtime - -### 配置 `.env` 并生成密钥 - -编辑[配置文件](#配置文件)中的 `.env`,填写以下必填项: - -- `BASE_DOMAIN` / `INFRA_HOST`:填写根域名,`INFRA_HOST` 通常与 `BASE_DOMAIN` 相同。 -- 镜像:按照[容器镜像](#容器镜像)中的清单填写;每个 Teable 镜像的 `` 从对应 GHCR package 页面的 Installation 命令复制; -- 密钥项,用下面的命令生成后填入: - -```bash -openssl rand -hex 32 # → OPENSANDBOX_API_KEY(整栈共用的一把 API Key) -openssl rand -hex 32 # → SESSION_SECRET(控制台会话签名) -openssl rand -hex 12 # → S3_ACCESS_KEY(内置 MinIO 模式;外部 S3 填供应商值) -openssl rand -hex 24 # → S3_SECRET_KEY(同上) -``` - -### 选择 TLS 模式 - -| 模式 | 准备 | 证书续期 | -|---|---|---| -| **static**(自备证书,默认) | 把 `fullchain.pem` + `privkey.pem` 放进 `certs/`。须为**单张多 SAN 证书**,覆盖 `` / `*.` / `*.app.` / `*.sandbox.` 四个名字(通配符只盖一级)。 | 手动:替换 `certs/` 文件后执行 `docker compose exec caddy caddy reload -c /etc/caddy/Caddyfile` | -| **cloudflare**(DNS-01 自动签发) | 域名 DNS 托管在 Cloudflare;准备一个 API Token(权限 **Zone:Read + DNS:Edit**)。 | Caddy 全自动 | - -**static 模式**:上方配置文件默认使用此模式,放好证书即可,无需其他改动。 -可用以下命令自检证书与私钥是否匹配、SAN 是否覆盖四个名字: - -```bash -openssl x509 -in certs/fullchain.pem -noout -text | grep -A1 'Subject Alternative Name' -diff <(openssl x509 -in certs/fullchain.pem -noout -pubkey) \ - <(openssl pkey -in certs/privkey.pem -pubout) && echo "证书与私钥匹配" -``` - -**cloudflare 模式**需做 4 处调整: - -1. 官方 Caddy 镜像不带 DNS 插件,先把下面内容存为 `Dockerfile.caddy` 并在本机构建一次: - - ```dockerfile Dockerfile.caddy - # 带 Cloudflare DNS 插件的 Caddy(通配证书须走 DNS-01,官方镜像无 DNS 插件)。 - # 请使用 Caddy 2.9.x。2.8.x 的 builder 会因 xcaddy 依赖漂移而编译失败。 - FROM caddy:2.9.1-builder AS builder - RUN xcaddy build --with github.com/caddy-dns/cloudflare - - FROM caddy:2.9.1 - COPY --from=builder /usr/bin/caddy /usr/bin/caddy - ``` - - ```bash - docker build -t teable-caddy-cloudflare:2.9.1 -f Dockerfile.caddy . - ``` - -2. `.env`:`CADDY_IMAGE=teable-caddy-cloudflare:2.9.1`,并填 `ACME_EMAIL` 与 `CLOUDFLARE_API_TOKEN`; -3. `Caddyfile`:按文件顶部注释,取消全局块(`email` + `acme_dns cloudflare`)的注释; -4. `Caddyfile`:删除 `(site_tls)` 片段里的 `tls /certs/...` 一行(证书改由 ACME 全自动管理)。 - -Cloudflare 模式必须持久化 `caddy-data` 卷。该卷用于存储证书和 ACME 账号;如果丢失,Caddy 会在重建后重新申请证书,可能触发 Let's Encrypt 的频率限制。 - -### 生成 Git 服务签名密钥 - -git 服务使用一对 Ed25519 密钥签发/验证访问令牌。多行 PEM 放不进 `.env`,因此通过 -`compose.override.yaml` 注入(Docker Compose 会自动加载它)。在部署目录执行: - -```bash -openssl genpkey -algorithm ed25519 -out git-jwt.key -openssl pkey -in git-jwt.key -pubout -out git-jwt.pub -{ - echo "services:" - echo " infra-service:" - echo " environment:" - echo " GIT_REGISTRY_JWT_PRIVATE_KEY: |" - sed 's/^/ /' git-jwt.key - echo " git-registry:" - echo " environment:" - echo " GIT_REGISTRY_JWT_PUBLIC_KEY: |" - sed 's/^/ /' git-jwt.pub -} > compose.override.yaml -rm -f git-jwt.key git-jwt.pub -``` - -生成的 `compose.override.yaml` 结构如下(PEM 内容为你生成的密钥): - -```yaml compose.override.yaml -services: - infra-service: - environment: - GIT_REGISTRY_JWT_PRIVATE_KEY: | - -----BEGIN PRIVATE KEY----- - ...你生成的私钥... - -----END PRIVATE KEY----- - git-registry: - environment: - GIT_REGISTRY_JWT_PUBLIC_KEY: | - -----BEGIN PUBLIC KEY----- - ...你生成的公钥... - -----END PUBLIC KEY----- -``` - -要轮换密钥,重新执行本节命令即可。 - -> 若 `openssl genpkey` 报 `Algorithm ed25519 not found`(如 macOS 自带的 LibreSSL), -> 可改用 Docker 生成密钥对,再执行上面的拼装命令: -> -> ```bash -> docker run --rm node:22-slim node -e 'const c=require("crypto");const{privateKey}=c.generateKeyPairSync("ed25519");process.stdout.write(privateKey.export({type:"pkcs8",format:"pem"}))' > git-jwt.key -> docker run --rm -v "$PWD:/w" node:22-slim node -e 'const c=require("crypto"),fs=require("fs");const k=c.createPrivateKey(fs.readFileSync("/w/git-jwt.key"));fs.writeFileSync("/w/git-jwt.pub",c.createPublicKey(k).export({type:"spki",format:"pem"}))' -> ``` - -### 创建共享工作区卷 - -Agent 共享工作区使用一个外部卷(compose 声明为 `external`,不会自动创建/删除): - -```bash -docker volume create teable-agent-juicefs -``` - -### 预拉取运行时镜像 - -全部为公开镜像,无需 docker login: - -```bash -set -a; . ./.env; set +a -docker pull "$OPENSANDBOX_SERVER_IMAGE" -docker pull "$SANDBOX_OPENSANDBOX_IMAGE" -docker pull "$APP_RUNTIME_DEFAULT_IMAGE" -docker pull ghcr.io/teableio/opensandbox-execd:v1.0.19-fix-1064 -docker pull opensandbox/egress:v1.0.12 -``` - -execd 和 egress 的镜像地址必须与 `opensandbox.toml` 中的配置完全一致。 - -### 启动服务 - -```bash -docker compose up -d -docker compose ps # 全部服务应为 running / healthy,minio-init 为 exited(0) - -# cloudflare 模式首次启动看证书签发(约十几秒~1分钟) -docker compose logs -f caddy -``` - -## 验证部署 - -### 检查服务状态 - -```bash -KEY=$(grep '^OPENSANDBOX_API_KEY=' .env | cut -d= -f2) - -# 沙箱引擎 -curl -s -H "OPEN-SANDBOX-API-KEY: $KEY" https:///v1/sandboxes; echo -# Infra API -curl -s -o /dev/null -w "health -> %{http_code}\n" https:///api/health -``` - -控制台 UI:浏览器打开 `https://`(或你填的 `INFRA_HOST`),用 api-key 登录, -key 即 `.env` 中的 `OPENSANDBOX_API_KEY`。 - -### 对接 Teable - -在 Teable 部署中添加以下环境变量: - -```bash -TEABLE_INFRA_API_URL=https:// -TEABLE_INFRA_API_KEY= -SANDBOX_OPENSANDBOX_IMAGE=ghcr.io/teableio/teable-sandbox-agent: -APP_DEPLOY_PROVIDER=docker-runtime -``` - -`TEABLE_INFRA_API_KEY` 必须与 Agent Runtime `.env` 中的 `OPENSANDBOX_API_KEY` 使用相同的值。`SANDBOX_OPENSANDBOX_IMAGE` 也必须与 Agent Runtime 配置的沙箱镜像一致。 - -`TEABLE_INFRA_API_URL` 必须指向 Teable Infra 根地址,不能包含路径。更新环境变量后,重启 Teable。 - -## 故障排查 - -| 问题 | 检查项 | +| 服务 | 职责 | |---|---| -| Teable 提示 Agent Runtime 未配置 | 确认上方 4 个 Teable 环境变量均已设置,然后重启 Teable。 | -| 沙箱或应用预览出现 DNS、TLS 或 404 错误 | 确认 4 条 DNS 记录均指向服务器,并且证书覆盖所有必需域名。 | -| 首次创建沙箱或部署应用较慢 | 在首次使用前预拉取全部运行时镜像。 | -| 服务状态异常 | 运行 `docker compose ps`,再通过 `docker compose logs ` 查看对应服务日志。 | - - -如需部署协助,请联系 **support@teable.ai**,并提供 `docker compose ps` 的输出和相关服务日志。 - - -## 日常运维 - -### 查看日志和管理服务 - -```bash -docker compose logs -f infra-service # / opensandbox-server / caddy / git-registry -docker compose restart infra-service -docker compose down # 停止服务,保留数据卷 -``` +| **沙箱引擎** | 每个 AI 会话都在独立的沙箱容器中运行 | +| **Infra Service** | 控制台与 API,Teable 应用的对接点;编排构建与应用部署 | +| **Git 仓库服务** | 你构建的应用的源码事实源(App Builder 推送至此) | +| **对象存储** | 附件与构建产物(S3 兼容) | +| **预览网关** | 在浏览器中提供沙箱预览与已部署应用的访问 | + +```mermaid +graph LR + subgraph "应用平面" + T["Teable 应用"] + end + subgraph "运行平面" + I["Infra Service"] + E["沙箱引擎"] + G["Git 仓库服务"] + S["对象存储"] + W["预览网关"] + end + U["浏览器"] --> T + T -- "AI 会话" --> I --> E + E -- "构建并推送" --> G + E -- "构建产物" --> S + U -- "预览与已部署应用" --> W + W --> E + + style T fill:#0D9373,stroke:#0a7a5e,color:#fff +``` + +AI 对话在沙箱内运行;App Builder 在同一个沙箱里构建,把源码推到 Git 仓库服务、 +把构建产物存入对象存储,部署后的应用经预览网关对外提供访问。 + +## 一个域名,四条 DNS 记录 + +一切派生自**一个基础域名** —— 通常是你组织域名的子域,例如 `teable.example.com`: + +| 记录 | 用途 | +|---|---| +| `` | Teable 应用 | +| `infra.` | Infra 控制台与 API —— git(`/git` 路径)与对象存储(桶路径)共用此主机 | +| `*.app.` | 你构建并部署的应用 | +| `*.sandbox.` | 浏览器中的沙箱预览 | -### 升级 +每个名字都只是默认值,任何主机名均可单独覆盖(见部署仓库的 values 示例)。 -修改 `.env` 中的镜像 tag,然后执行 `docker compose pull && docker compose up -d`。 +## Agent 如何与应用保持同步 -沙箱、应用基准镜像或 execd/egress 更新后,请按照[预拉取运行时镜像](#预拉取运行时镜像)重新拉取。 +沙箱 agent 镜像配置为**不带 tag 的前缀**。Teable 应用启动 AI 会话时会拼上 +**自身的发布版本 tag**,因此 agent 永远与应用版本配对 —— 并且应用会经 +Infra Service 预热这个镜像,首次会话不必现场拉取数 GB 镜像。 +你永远不需要手动拉取或钉定 agent。 -### 备份数据 +## 版本体系 -建议定期备份以下数据卷,尤其是 `minio-data` 与 `caddy-data`。 +运行平面以部署仓库的**平台版本**(`v<年>.<月>.<序号>`)形式发布: -| 卷 | 内容 | -|---|---| -| `opensandbox-state` | 沙箱引擎状态(SQLite) | -| `infra-state` | App Runtime 状态与草稿 | -| `git-registry-data` | git 仓库数据 | -| `minio-data` | 制品文件(内置 MinIO 模式) | -| `teable-agent-juicefs` | Agent 共享工作区(external 卷,部署时创建) | -| `caddy-data` / `caddy-config` | TLS 证书、ACME 账号、Caddy 配置 | +- **tag** 是已验证的快照:`versions.yaml` 钉定每个组件镜像(含 digest), + 仓库的 `CHANGELOG.md` 写明每版变了什么、你需要做什么; +- 仓库 `main` 分支是滚动最新; +- 自带的 **doctor** 脚本会把你实际运行的组合与版本清单比对,给出三态结论: + 兼容 / 需升级 Teable 应用 / 未知(未经验证的)组合。 -`docker compose down -v` 会同时删除数据卷,包括制品、Git 仓库和证书。请谨慎使用。 +Teable 应用有自己独立的发版线(日期型 tag;`latest` 为稳定通道)—— +见[版本升级](/zh/deploy/upgrade)。平台版本会声明应用兼容窗口,doctor 替你检查。 -## 安全与限制 +## 部署 -### 安全须知 +两条路径安装的是同一套平台,部署仓库内有完整指引: -- 含密钥的文件只有 `.env` 与 `compose.override.yaml`,**切勿提交到版本库或外发**。 -- 整栈共用一把 `OPENSANDBOX_API_KEY`;如需分权,请分别替换各服务的对应环境变量。 -- `docker.sock` 挂载进 opensandbox-server 与 infra-service(Docker 后端必需),等价于宿主 root - 权限,请只在专用、可信的主机上部署。 -- 唯一公网面是 Caddy 的 80/443;MinIO 控制台(9001)不对外,需要时用 SSH 隧道访问。 -- 沙箱与宿主共享内核,**不是多租户强隔离边界**;对外开放多租户场景请为沙箱引擎配置 - gVisor / Kata 等强隔离运行时。 + + + 一台机器装下一切 —— 首次全功能部署,支持 local / server 两种模式。 + + + 一个伞形 chart 装进现有集群;只需设置 global.baseDomain。 + + -### 能力边界 +相关主题(均在部署仓库中维护): -**可用**:沙箱引擎全功能;应用主链路(创建 → 激活 → 网关访问 → 删除,含 dashboard、日志、 -S3 制品);git 推拉;控制台 api-key 登录 + Infra API;文件页与 S3 兼容接口。 +- **已在运行基础版 Teable?** 数据原地不动,运行平面挂在旁边即可: + [迁移指南](https://github.com/teableio/teable-deployment/blob/main/migration/2026-07-basic-to-full-featured.md) +- **私有/企业 CA**:沙箱必须信任你入口的证书 —— + [private-ca.md](https://github.com/teableio/teable-deployment/blob/main/helm/private-ca.md) +- **容量、版本与镜像加速**:[VERSIONS.md](https://github.com/teableio/teable-deployment/blob/main/VERSIONS.md) · + [images/README.md](https://github.com/teableio/teable-deployment/blob/main/images/README.md) +- **出问题时**:先跑 doctor,再看 + [TROUBLESHOOTING.md](https://github.com/teableio/teable-deployment/blob/main/TROUBLESHOOTING.md) -**不可用 / 受限**(Docker 模式取舍):应用 `host-prefix` 改名(返回 501)、`scale-to-zero` -闲置缩容(no-op)、引擎 key 轮换 / server 滚动升级、控制台 Kubernetes 观测页(显示为空属正常)。 +部署完成后,用 `TEABLE_INFRA_API_URL` / `TEABLE_INFRA_API_KEY` 把 Teable 应用接到 +运行平面(部署指引中有说明),然后在 +[管理面板 → Sandbox Agent](/zh/basic/admin-panel/sandbox-agent) 配置运行限制。 diff --git a/zh/deploy/aws.mdx b/zh/deploy/aws.mdx index ac6d30ba..b5134e6f 100644 --- a/zh/deploy/aws.mdx +++ b/zh/deploy/aws.mdx @@ -211,8 +211,6 @@ BACKEND_STORAGE_PUBLIC_BUCKET=teable-public-<你的唯一后缀> BACKEND_STORAGE_PRIVATE_BUCKET=teable-private-<你的唯一后缀> STORAGE_PREFIX=https://teable-public-<你的唯一后缀>.s3.us-west-2.amazonaws.com -# 可选:Sandbox 服务(如果单独部署) -# SANDBOX_URL=http://teable-sandbox:7070 ``` diff --git a/zh/deploy/azure.mdx b/zh/deploy/azure.mdx index 4890175d..b3f09892 100644 --- a/zh/deploy/azure.mdx +++ b/zh/deploy/azure.mdx @@ -816,5 +816,5 @@ az aks enable-addons \ - [私有化部署概览](/zh/deploy/production-overview) — 架构、配置与扩展 - [环境变量参考](/zh/deploy/env) - [对象存储(S3-compatible)](/zh/deploy/storage) -- [Kubernetes 部署](/zh/deploy/k8s)(通用 K8s 指南) +- [全功能平台](/zh/deploy/agent-runtime)(AI 能力与 App Builder) - [AWS 部署](/zh/deploy/aws)(如使用跨云存储需参考 S3 设置) diff --git a/zh/deploy/choose.mdx b/zh/deploy/choose.mdx new file mode 100644 index 00000000..cf64b29b --- /dev/null +++ b/zh/deploy/choose.mdx @@ -0,0 +1,21 @@ +--- +title: "选择部署方式" +description: "在 Teable Cloud、基础版自部署与全功能自部署之间做选择。" +--- + +运行 Teable 有三种方式,按需选择: + +| | **Teable Cloud** | **基础版自部署** | **全功能自部署** | +|---|---|---|---| +| 表格、协作、API、自动化 | ✅ | ✅ | ✅ | +| AI 能力(对话、智能体) | ✅ | ❌ | ✅ | +| App Builder(构建并部署应用) | ✅ | ❌ | ✅ | +| 沙箱 / 预览 | ✅ | ❌ | ✅ | +| 运行位置 | [teable.ai](https://teable.ai) —— 无需部署 | 一台机器:应用 + PostgreSQL | 一台机器(Docker)或 Kubernetes 集群 | +| 从这里开始 | [teable.ai](https://teable.ai) | [Docker 部署](/zh/deploy/docker) | [Agent Runtime 架构](/zh/deploy/agent-runtime) → [teableio/teable-deployment](https://github.com/teableio/teable-deployment) | + + +基础版与全功能不是二选一的岔路:全功能平台是**在你现有基础版 Teable 旁边挂载一个 +运行平面** —— 业务数据原地不动。今天先跑基础版,以后随时加 AI: +[迁移指南](https://github.com/teableio/teable-deployment/blob/main/migration/2026-07-basic-to-full-featured.md)。 + diff --git a/zh/deploy/docker.mdx b/zh/deploy/docker.mdx index 4ad3cbf5..94e2fd82 100644 --- a/zh/deploy/docker.mdx +++ b/zh/deploy/docker.mdx @@ -6,7 +6,7 @@ description: "使用 Docker Compose 部署 Teable,并配置常用部署选项 {/* mintlify-resync: 2026-05-14 */} **Docker Compose 适合小团队和评估测试**(50 人以内)。 -生产环境部署请先阅读 [概览](/zh/deploy/production-overview),然后选择 [Kubernetes](/zh/deploy/k8s) 或云服务部署方案。 +生产环境部署请先阅读 [概览](/zh/deploy/production-overview) 或云服务部署方案。需要 AI 能力或 App Builder?请改用[全功能平台](/zh/deploy/agent-runtime)。 ## Docker Compose 部署 diff --git a/zh/deploy/k8s.mdx b/zh/deploy/k8s.mdx deleted file mode 100644 index 92c6099b..00000000 --- a/zh/deploy/k8s.mdx +++ /dev/null @@ -1,429 +0,0 @@ ---- -title: "K8s 部署" ---- - - -**推荐场景**:50+ 用户的生产环境 - - - -配置建议(CPU、内存、副本数)请参考 [私有化部署概览 → 配置建议](/zh/deploy/production-overview#配置建议)。 - - -## 前置条件 -### Teable 镜像 -- 应用镜像: `registry.cn-shenzhen.aliyuncs.com/teable/teable:latest` - -### 运行环境 -- 运行中的 Kubernetes 集群 - -### 已配置的外部服务 -#### 数据库服务 -- PostgreSQL 数据库: postgres:15.4(版本号大于 12) - - PostgreSQL 镜像: `registry.cn-shenzhen.aliyuncs.com/teable/postgres:15.4` - -#### 缓存服务 -- Redis 缓存服务: redis:7.2.4(版本号大于 5) - - Redis 镜像: `registry.cn-shenzhen.aliyuncs.com/teable/redis:7.2.4` - -#### 对象存储服务 -- MinIO 对象存储服务: minio:RELEASE.2024-10-13T13-34-11Z-cpuv1 - - MinIO 镜像: `registry.cn-shenzhen.aliyuncs.com/teable/minio:RELEASE.2024-10-13T13-34-11Z-cpuv1` - - 注意: 可以直接使用最新版本,或者使用云存储,配置参考,下方示例使用 minio 来做例子。 - -### 必要工具 -- kubectl 命令行工具 - -## 依赖组件配置 -### 文件存储 MinIO -文件存储服务需要支持外网访问(即终端用户可直接访问到) - -文件存储需要预先创建好两个 bucket: - -| Bucket | 环境变量 | 权限 | -|--------|---------|------| -| 公共桶 | `BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub` | **公共可读(必须开启匿名访问)** | -| 私有桶 | `BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt` | 私有(默认) | - - -公共桶**必须**开启匿名读取权限。否则用户将无法查看共享图片、头像等公开文件。 - - -#### 使用 MinIO Client (mc) 创建 Bucket - -在部署 Teable 之前,请先创建所需的 bucket: - -```sh -# 设置 MinIO 客户端别名 -mc alias set teable-minio https://minio.example.com YOUR_ACCESS_KEY YOUR_SECRET_KEY - -# 创建公共桶并设置匿名读取权限 -mc mb --ignore-existing teable-minio/teable-pub -mc anonymous set public teable-minio/teable-pub - -# 创建私有桶 -mc mb --ignore-existing teable-minio/teable-pvt - -# 验证 bucket 是否创建成功 -mc ls teable-minio -# 预期输出: -# [2024-01-01 00:00:00 UTC] 0B teable-pub/ -# [2024-01-01 00:00:00 UTC] 0B teable-pvt/ -``` - -#### Teable MinIO 环境变量总览 - -```sh -# 固定值 -BACKEND_STORAGE_PROVIDER=minio -# 公共桶 -BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub -# 私有桶 -BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt -# 外网端点,重要!需要终端用户可以直接访问到 -BACKEND_STORAGE_MINIO_ENDPOINT=minio.example.com -# 与前面的值保持一样,但是要加个协议名称 -STORAGE_PREFIX=https://minio.example.com -# 内网端点(Kubernetes DNS 格式:..svc.cluster.local) -# ⚠️ 请将 替换为实际的命名空间,例如:minio.teable.svc.cluster.local -BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT=minio.teable.svc.cluster.local -# 外网端口,一般为 443 或者 9000 -BACKEND_STORAGE_MINIO_PORT=443 -# 内网端口,一般为 80 或者 9000 -BACKEND_STORAGE_MINIO_INTERNAL_PORT=80 -# 是否启用 HTTPS, 注意如果 Teable 启用了 https 则 minio 也必须启用 https 否则有跨域问题 -BACKEND_STORAGE_MINIO_USE_SSL="true" -# 管理员账号 -BACKEND_STORAGE_MINIO_ACCESS_KEY=root -# 管理员密码 -BACKEND_STORAGE_MINIO_SECRET_KEY=rootPassword -``` - -### 数据库 -需要创建一个具有管理员权限的数据库账户,以及一个数据库,并且设置密码。 -对应环境变量示例: - - 数据库名称: teable - - 密码:your-password - - 账号: postgres - - 端口: 5432 -```sh -PRISMA_DATABASE_URL="postgresql://postgres:your-password@your-postgres-host:5432/teable" -``` - -### 缓存 Redis -Teable 配置 redis 缓存只需要提供内网连接地址即可。(注意 Redis 不仅管理缓存还管理队列,不可或缺。数据需要定时备份) -对应环境变量示例 -```sh -BACKEND_CACHE_REDIS_URI="redis://username:password@your-redis-host:6379/0" -``` - -## 创建配置文件 - -```yaml teable-config.yaml(非敏感配置) -apiVersion: v1 -kind: ConfigMap -metadata: - name: teable-config - namespace: teable # 请替换为你的命名空间 -data: - # 应用基础配置,对外访问的域名 - PUBLIC_ORIGIN: "https://your-domain.com" - - # 存储配置 - BACKEND_STORAGE_PROVIDER: "minio" - # Bucket 名称(需要在部署前于 MinIO 中创建) - BACKEND_STORAGE_PUBLIC_BUCKET: "teable-pub" - BACKEND_STORAGE_PRIVATE_BUCKET: "teable-pvt" - # 外网端点,重要!需要终端用户可以直接访问到 - BACKEND_STORAGE_MINIO_ENDPOINT: "minio.example.com" - # 与前面的值保持一样,但是要加个协议名称 - STORAGE_PREFIX: "https://minio.example.com" - # 内网端点(Kubernetes DNS 格式:..svc.cluster.local) - # ⚠️ 请将 'teable' 替换为实际的命名空间 - BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT: "minio.teable.svc.cluster.local" - # 外网端口,一般为 443 或者 9000 - BACKEND_STORAGE_MINIO_PORT: "443" - # 内网端口,一般为 80 或者 9000 - BACKEND_STORAGE_MINIO_INTERNAL_PORT: "80" - # 是否启用 HTTPS, 注意如果 Teable 启用了 https 则 minio 也必须启用 https 否则有跨域问题 - BACKEND_STORAGE_MINIO_USE_SSL: "true" - - # 缓存配置, 固定值 - BACKEND_CACHE_PROVIDER: "redis" - - # 其他配置,固定值 - NEXT_ENV_IMAGES_ALL_REMOTE: "true" - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - # 使用自签发证书需要保留这个 - NODE_TLS_REJECT_UNAUTHORIZED: '0' -``` - -```yaml teable-secrets.yaml(敏感信息) -apiVersion: v1 -kind: Secret -metadata: - name: teable-secrets - namespace: teable # 请替换为你的命名空间 -type: Opaque -stringData: - # 数据库敏感信息 - PRISMA_DATABASE_URL: "postgresql://postgres:your-password@your-postgres-host:5432/teable" - - # 应用密钥 - BACKEND_JWT_SECRET: "your-jwt-secret" - BACKEND_SESSION_SECRET: "your-session-secret" - - # MinIO 认证信息 - BACKEND_STORAGE_MINIO_ACCESS_KEY: "your-minio-access-key" - BACKEND_STORAGE_MINIO_SECRET_KEY: "your-minio-secret-key" - - # Redis 认证信息 - BACKEND_CACHE_REDIS_URI: "redis://username:password@your-redis-host:6379/0" -``` - - -```yaml teable-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: teable - namespace: teable # 请替换为你的命名空间 -spec: - replicas: 1 # 根据需要配置 - selector: - matchLabels: - app: teable - template: - metadata: - labels: - app: teable - spec: - # Add initContainers for database migration - initContainers: - - name: db-migrate - image: registry.cn-shenzhen.aliyuncs.com/teable/teable:latest - args: - - migrate-only - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - requests: - cpu: 100m - memory: 102Mi - limits: - cpu: 1000m - memory: 1024Mi - containers: - - name: teable - image: registry.cn-shenzhen.aliyuncs.com/teable/teable:latest - args: - - skip-migrate - ports: - - containerPort: 3000 - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - requests: - cpu: 200m - memory: 400Mi - limits: - cpu: 2000m - memory: 4096Mi - startupProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 10 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 30 - successThreshold: 1 - livenessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 30 - periodSeconds: 30 - timeoutSeconds: 5 - failureThreshold: 3 - successThreshold: 1 - readinessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 15 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 3 - successThreshold: 1 -``` - -```yaml teable-service.yaml -apiVersion: v1 -kind: Service -metadata: - name: teable - namespace: teable # 请替换为你的命名空间 -spec: - ports: - - port: 3000 - targetPort: 3000 - selector: - app: teable -``` - -```yaml teable-ingress.yaml (可选) -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: teable - namespace: teable # 请替换为你的命名空间 - # 以 nginx 做示例,如果使用其他的 ingress class 请替换 - annotations: - nginx.ingress.kubernetes.io/proxy-body-size: "100m" - nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" - nginx.ingress.kubernetes.io/proxy-send-timeout: "600" - nginx.ingress.kubernetes.io/proxy-read-timeout: "600" - nginx.ingress.kubernetes.io/proxy-request-buffering: "off" -spec: - ingressClassName: nginx # 如果使用其他 ingress class 请修改 - rules: - - host: your-domain.com # 请替换为你的域名 - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: teable - port: - number: 3000 - tls: - - hosts: - - your-domain.com # 请替换为你的域名 - secretName: your-tls-secret -``` - - -**请勿**添加 `nginx.ingress.kubernetes.io/rewrite-target` 注解,这会破坏 Teable 的内部路由。 - - - -## 部署步骤 - - -在部署 Teable 之前,请确保: -1. MinIO bucket(`teable-pub` 和 `teable-pvt`)已创建 -2. 公共桶已开启匿名读取权限 -3. PostgreSQL 数据库 `teable` 已存在 -4. Redis 服务可访问 - - -1. 创建命名空间(如果不存在): -```sh -kubectl create namespace teable -``` - -2. 创建配置和密钥: -```sh -kubectl apply -f teable-config.yaml -kubectl apply -f teable-secrets.yaml -``` - -3. 部署应用: -```sh -kubectl apply -f teable-deployment.yaml -kubectl apply -f teable-service.yaml -kubectl apply -f teable-ingress.yaml # 可选,如果使用 Ingress -``` - -4. 验证部署: -```sh -# 检查 Pod 状态 -kubectl get pods -n teable -l app=teable - -# 等待 Pod 就绪 -kubectl rollout status deployment/teable -n teable - -# 查看应用日志 -kubectl logs -n teable -l app=teable -``` - -### 配置注意事项 - -1. 敏感信息管理 - - 所有密码、密钥类信息应通过 Secrets 管理 - -2. 数据库配置 - - 确保数据库已创建 teable 数据库 - - 数据库用户需要具备适当权限 - - 建议使用连接池管理数据库连接 - -3. MinIO 配置 - - 确保存储桶已创建且权限正确, 一个公共桶,一个私有桶 - - 公共桶需要完全公开读 - - 内外部访问地址配置正确 - -4. Redis 配置 - - 建议启用 Redis 持久化 - - 配置适当的内存限制 - - 考虑使用 Redis 集群提高可用性 - -5. 安全建议 - - 使用强密码和密钥 - - 启用 TLS/SSL 加密 - - 定期更新证书 - - 限制网络访问范围 - -6. 资源配置 - - 根据实际负载调整资源限制 - - 监控资源使用情况 - - 配置适当的健康检查参数 - -### 故障排查 - -#### 常见问题 - -| 错误信息 | 原因 | 解决方案 | -|---------|------|---------| -| `ECONNREFUSED` 连接 MinIO 失败 | MinIO 内网端点配置错误或不可达 | 检查 `BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT` 格式是否正确:`..svc.cluster.local` | -| `Bucket not found` | MinIO bucket 未创建 | 在部署前使用 `mc mb` 命令创建 bucket | -| `Access Denied` 文件上传失败 | MinIO 凭证错误 | 检查 `BACKEND_STORAGE_MINIO_ACCESS_KEY` 和 `SECRET_KEY` | -| 访问页面显示 `Page not found` | Ingress 配置了 `rewrite-target` | 移除 Ingress 中的 `rewrite-target` 注解 | -| Pod 处于 `CrashLoopBackOff` 状态 | 后端启动失败 | 检查日志,确认 MinIO、数据库、Redis 连接是否正常 | - -#### 诊断命令 - -1. 检查 Pod 状态: -```sh -kubectl describe pod -n teable -l app=teable -``` - -2. 查看应用日志: -```sh -kubectl logs -n teable -l app=teable -kubectl logs -n teable -l app=teable --previous # 如果 Pod 崩溃 -``` - -3. 验证配置: -```sh -kubectl describe configmap teable-config -n teable -kubectl describe secret teable-secrets -n teable -``` - -4. 检查网络连接: -```sh -kubectl exec -it -n teable -- curl -v localhost:3000/health -``` - -5. 测试 MinIO 集群内部连接: -```sh -kubectl run test-minio --rm -it --image=curlimages/curl --restart=Never -n teable -- \ - curl -v http://minio.teable.svc.cluster.local/minio/health/live -``` diff --git a/zh/deploy/one-key.mdx b/zh/deploy/one-key.mdx deleted file mode 100644 index 42413fb5..00000000 --- a/zh/deploy/one-key.mdx +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: "一键云部署" -description: "通过云服务商集成的配置模板,你可以一键部署完整的,可伸缩的 Teable 服务" ---- -| 优点 | 缺点 | -| ----------- | ------ | -| 无需任何配置,一键启动 | 依赖云服务商 | -| 可以弹性伸缩 | | -| 运维简单 | | - -**系统要求**: - -* 应用节点最低 1c/2g -* 数据库最低 1c/2g - -#### Sealos - -有免费额度,对国内支持友好,按量计费 - -[**一键部署**](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dteable) - -### 订阅与激活(付费功能) - -如果你是**自托管部署**并需要付费功能,请在部署完成后进行订阅并激活许可证: - -1. 在你部署好的 Teable 实例中进入管理员后台,复制 **Instance ID(实例 ID)** -2. 打开自托管订阅页面完成订阅:`https://app.teable.ai/public/pricing?host=self-hosted` -3. 订阅成功后会获得 **License Key(许可证密钥)**,回到实例中完成激活 - -详情请见:[订阅和激活许可证](/zh/deploy/activate) - -### 下一步 - -[配置邮件服务](/zh/deploy/email) - -[配置外部数据库连接](/zh/deploy/database-connection) diff --git a/zh/deploy/production-overview.mdx b/zh/deploy/production-overview.mdx index 76341bf2..21928b32 100644 --- a/zh/deploy/production-overview.mdx +++ b/zh/deploy/production-overview.mdx @@ -7,6 +7,11 @@ description: "Teable 生产环境部署的架构、配置建议和部署路径 **推荐场景**:50+ 用户的生产环境 + +本页介绍**基础版** Teable 应用的规模化部署。如需 AI 能力或 App Builder, +请改用[全功能平台](/zh/deploy/agent-runtime) —— 它包含应用与 AI 运行平面。 + + ## 架构概览 Teable 采用**无状态应用层**配合**外部状态服务**的设计,支持水平扩展和高可用。 @@ -149,8 +154,8 @@ Teable 使用**两个独立的 Redis 实例**: ## 下一步:选择部署平台 - - 在任意 K8s 集群上部署,完全掌控基础设施。 + + 应用 + AI 运行平面,一个 Helm chart 装进任意 K8s 集群。 ECS + RDS + ElastiCache + S3,推荐 50+ 用户使用。 diff --git a/zh/deploy/upgrade.mdx b/zh/deploy/upgrade.mdx index 7fcac066..5b30e8d0 100644 --- a/zh/deploy/upgrade.mdx +++ b/zh/deploy/upgrade.mdx @@ -4,162 +4,122 @@ title: "版本更新" 本文档介绍如何更新您的 Teable 私有化部署版本。 -## 更新频率 +## 发布通道与版本号 -Teable 的更新发布频率较高且不固定,一周内可能会发布多个更新版本。Teable 私有化版本的更新完全由您自主决定,您可以选择每周或每月进行一次升级。只要有新版本发布,您都可以灵活决定是否更新。您可以在 [GitHub Packages](https://github.com/teableio/teable-enterprise/pkgs/container/teable-ee) 查看所有可用版本。 +Teable 应用以**日期型 release tag** 发布,形如 +`release.<时间戳>.<构建号>`(例如 `release.2026-07-14T12-24-39Z.2228`), +另有两个浮动通道: - -在进行任何更新操作之前,强烈建议先备份您的数据。 - - -## Docker Compose 更新 - -如果您使用 Docker Compose 部署 Teable,请按以下步骤更新: - -### 备份数据(推荐) +| Tag | 含义 | +|---|---| +| `latest` | **稳定**通道 —— 始终指向最新的稳定版本 | +| `beta` | 滚动通道 —— 最新构建,先于稳定版 | +| `release.<时间戳>.<构建号>` | 不可变的具体版本 | -我们建议直接备份整个云主机,或者备份使用到的 PostgreSQL / Redis / 存储卷。 - -### 进入部署目录 - -```bash -cd teable # 进入您的 teable 部署目录 -``` +发布频率较高(常常一周多个版本),何时升级由您自主决定。 +全部版本可在 [GitHub Packages](https://github.com/teableio/teable/pkgs/container/teable) 查看。 -### 拉取最新镜像 + +在进行任何更新操作之前,强烈建议先备份您的数据。 + -```bash -docker-compose pull -``` +## 基础版:Docker Compose 更新 -### 重启服务 +如果您使用 Docker Compose 部署基础版 Teable: ```bash -docker-compose up -d +cd teable # 您的部署目录 +docker compose pull +docker compose up -d ``` -系统会自动使用最新的镜像重新创建容器。您的数据保存在 Docker 卷中,不会丢失。 +容器将基于新镜像重建;数据存储在 Docker 卷(或外部数据库)中,不受影响。 -### 验证更新 +验证: ```bash -# 查看容器状态 -docker-compose ps - -# 查看日志确认启动正常 -docker-compose logs -f teable +docker compose ps +docker compose logs -f teable ``` -访问您的 Teable 实例,确认服务正常运行。 +### 钉定特定版本 -## 更新到指定版本 - -如果您希望更新到特定版本而不是最新版本,可以修改 `docker-compose.yaml` 中的镜像标签: +如需运行特定版本而非 `latest` 通道,在 `docker-compose.yaml` 中指定镜像 tag: ```yaml services: teable: - image: registry.cn-shenzhen.aliyuncs.com/teable/teable:0.5.0-alpha-1.x.x-build.xxx # 指定版本号 + image: ghcr.io/teableio/teable:release.2026-07-14T12-24-39Z.2228 ``` -然后执行: - -```bash -docker-compose pull -docker-compose up -d -``` +然后执行 `docker compose pull && docker compose up -d`。 +生产环境建议钉版:版本何时变化完全由您掌控。 - -您可以在 [GitHub Releases](https://github.com/teableio/teable-enterprise/pkgs/container/teable-ee) 页面查看所有可用版本。 - +## 全功能部署(AI 运行平面) -## Kubernetes 更新 +全功能部署(见 [Agent Runtime 架构](/zh/deploy/agent-runtime))有**两条版本线**: -如果您使用 Kubernetes 部署 Teable,请按以下步骤更新: +- **Teable 应用** —— 即上述日期型 tag,按部署仓库的指引升级; +- **平台本身** —— + [teableio/teable-deployment](https://github.com/teableio/teable-deployment) + 仓库以**平台版本**(`v<年>.<月>.<序号>`)发布:每个 git tag 是全部运行面组件的 + 已验证快照,[`CHANGELOG.md`](https://github.com/teableio/teable-deployment/blob/main/CHANGELOG.md) + 写明每版变了什么、你需要做什么(多数版本可热切换)。升级时**从 checkout 到 + 对应 tag 的仓库副本操作**,参照 + [`VERSIONS.md`](https://github.com/teableio/teable-deployment/blob/main/VERSIONS.md)。 -### 手动更新镜像 - -如果您使用自定义的 Kubernetes 配置,可以直接更新 Deployment 中的镜像版本: - -```bash -kubectl set image deployment/teable teable=registry.cn-shenzhen.aliyuncs.com/teable/teable:0.5.0-alpha-1.x.x-build.xxx -n teable -``` - -### 验证更新 - -```bash -# 查看 Pod 状态 -kubectl get pods -n teable - -# 查看滚动更新状态 -kubectl rollout status deployment/teable -n teable - -# 查看日志 -kubectl logs -f deployment/teable -n teable -``` +任一条线升级后,运行自带的 **doctor** —— 它检查健康状态,并把实际运行的组合与 +平台版本清单比对(兼容 / 需升级 Teable 应用 / 未知组合)。 ## 数据库迁移 -Teable 在启动时会自动执行数据库迁移,无需手动干预。如果您在更新后遇到问题,可以查看日志确认迁移是否成功: +Teable 启动时自动执行数据库迁移,无需人工干预。如升级后出现异常,先查日志: ```bash -# Docker Compose -docker-compose logs teable | grep -i migration - -# Kubernetes -kubectl logs deployment/teable -n teable | grep -i migration +docker compose logs teable | grep -i migration ``` -## 回滚版本 - -如果更新后遇到问题,您可以回滚到之前的版本。 - -### Docker Compose 回滚 +## 回滚 -1. 修改 `docker-compose.yaml` 中的镜像标签为之前的版本 -2. 执行 `docker-compose up -d` +如果升级后遇到问题: -### Kubernetes 回滚 +1. 将 `docker-compose.yaml` 中的镜像 tag 改回上一个版本号 + (这正是钉版优于 `latest` 的原因:上一个版本是白纸黑字记着的); +2. `docker compose up -d` -```bash -# 查看历史版本 -kubectl rollout history deployment/teable -n teable - -# 回滚到上一个版本 -kubectl rollout undo deployment/teable -n teable - -# 回滚到指定版本 -kubectl rollout undo deployment/teable -n teable --to-revision= -``` + +若某次升级执行过数据库结构迁移,直接回滚可能不安全 —— 此时应从升级前的备份恢复。 +这也是上文强烈建议备份的主要原因。 + ## 常见问题 - -不会。您的数据存储在 Docker 卷或外部数据库中,更新容器不会影响数据。但仍建议在更新前进行备份。 + +不会。您的数据存储在 Docker 卷或外部数据库中,更新容器不会影响数据。但仍建议在更新前备份。 - -不会。您的实例 ID 在应用更新过程中保持不变,它是您自托管安装的永久标识符。 + +不会。应用更新期间 Instance ID 保持不变,它是您私有化安装的永久标识。 - -通常情况下,拉取新镜像需要几分钟(取决于网络速度),容器重启只需要几秒钟。整个过程通常在 5-10 分钟内完成。 + +通常拉取新镜像需要几分钟(取决于网速),容器重启只需几秒。整个过程一般在 5-10 分钟内完成。 - -使用 `docker-compose up -d` 或 Kubernetes 滚动更新时,会有短暂的服务中断(通常几秒到几十秒)。如果需要零停机更新,建议使用 Kubernetes 并配置适当的滚动更新策略。 + +使用 `docker compose up -d` 会有短暂的服务中断(通常几秒到几十秒)。 - -您可以通过以下方式查看当前版本: -- 在 Teable 界面左下角查看版本号 -- 使用管理员账号进入管理面板查看 -- 执行 `docker inspect teable-teable-1 --format='{{.Config.Image}}'` 查看镜像版本 + +您可以通过以下方式查看当前版本: +- 查看 Teable 界面左下角的版本号 +- 使用管理员账号访问管理面板 +- 运行 `docker inspect <容器名> --format='{{.Config.Image}}'` 查看镜像版本。若您运行的是 `latest` 通道,全功能部署自带 `pin-image.sh` 工具可解析 `latest` 当前对应的具体版本。 - -1. 首先查看容器日志排查问题:`docker-compose logs teable` -2. 如果是数据库迁移问题,尝试从备份恢复数据 -3. 如果问题持续,可以回滚到之前的版本 -4. 联系支持团队 support@teable.ai + +1. 先查看容器日志排查:`docker compose logs teable` +2. 如果是数据库迁移问题,尝试从备份恢复 +3. 问题持续存在时,回滚到上一版本 +4. 联系支持:support@teable.ai From 861d0c9dd4b8f12b62050dd65208c0e8848c91b8 Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 19:23:31 +0800 Subject: [PATCH 02/12] docs(env): document the AI runtime-plane connection group; drop retired vars - clarify scope: these are the Teable app's own variables; runtime plane services are configured by the teable-deployment assets - new group: AI & App Builder (SANDBOX_PROVIDER, TEABLE_INFRA_API_URL/KEY/ BUCKET, agent image prefix semantics, runtime type, server proxy, APP_DEPLOY_PROVIDER, sandbox JWT secret and per-sandbox resources) with a note that full-featured deployments set them automatically - new security rows: storage/access-token encryption key+IV pairs, whose defaults are public constants in source and must be regenerated per host - removed vars no longer read anywhere: NEXT_ENV_IMAGES_ALL_REMOTE, MAX_SPACE_OWNER_COUNT - en + zh in sync Co-Authored-By: Claude Fable 5 --- en/deploy/env.mdx | 39 +++++++++++++++++++++++++++++---------- zh/deploy/env.mdx | 36 ++++++++++++++++++++++++++---------- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/en/deploy/env.mdx b/en/deploy/env.mdx index 84385bac..4c9b8e2c 100644 --- a/en/deploy/env.mdx +++ b/en/deploy/env.mdx @@ -4,19 +4,30 @@ description: "Here are all available environment variables in Teable and their e mode: "wide" --- -{/* mintlify-resync: 2026-05-14 */} - -For production deployments, use **S3-compatible object storage** (`s3` / `minio`) with **public/private dual buckets**. Also ensure the public bucket is configured with **public-read + CORS**. See: [Object Storage (S3-compatible)](/en/deploy/storage). - +These are the **Teable app's own** environment variables. The services of the +full-featured runtime plane (sandbox engine, Infra Service, git registry, …) +are configured separately by the deployment assets in +[teableio/teable-deployment](https://github.com/teableio/teable-deployment) — +see [Agent Runtime Architecture](/en/deploy/agent-runtime). + + +If you deploy with the full-featured assets (Docker all-in-one or the Helm +chart), the **AI & App Builder** variables below are set for you automatically. +Set them by hand only when attaching an existing standalone Teable to a +runtime plane. + | Environment Variable | Description | Default Value | Required | Example | | ----------------------------------------- | -------------------------------------------------------------------------- | --------------- | -------- | ------------------------------------------------- | | **Core Configuration** | | | | | | PUBLIC_ORIGIN | Public origin for generating complete URLs, must be set to your app's access address | - | Yes | https://app.teable.ai | | SECRET_KEY | Key for JWT, sessions, and sharing, use a strong password | defaultSecretKey | Yes | yourStrongSecretKey | +| BACKEND_STORAGE_ENCRYPTION_KEY | Encryption key for storage tokens. The default is a public constant in the source — production deployments must set their own random value (the full-featured assets generate one per host) | public constant | - | 16-char random string | +| BACKEND_STORAGE_ENCRYPTION_IV | Encryption IV paired with BACKEND_STORAGE_ENCRYPTION_KEY | public constant | - | 16-char random string | +| BACKEND_ACCESS_TOKEN_ENCRYPTION_KEY | Encryption key for personal access tokens. Same caveat: the default is public — set your own in production | public constant | - | 16-char random string | +| BACKEND_ACCESS_TOKEN_ENCRYPTION_IV | Encryption IV paired with BACKEND_ACCESS_TOKEN_ENCRYPTION_KEY | public constant | - | 16-char random string | | PORT | Port on which the application runs | 3000 | - | 3000 | | LOG_LEVEL | Log level, options: fatal, error, warn, info, debug, trace | info | - | debug | -| NEXT_ENV_IMAGES_ALL_REMOTE | Whether to allow loading third-party images | false | - | true | | **Storage Configuration** | | | | | | BACKEND_STORAGE_PROVIDER | Storage provider, options: local, minio, s3, aliyun | local | - | s3 | | BACKEND_STORAGE_LOCAL_PATH | Local storage path | .assets/uploads | - | .assets/uploads | @@ -79,7 +90,6 @@ For production deployments, use **S3-compatible object storage** (`s3` / `minio` | MAX_COPY_CELLS | Maximum number of cells to copy in a single request | - | - | 50000 | | MAX_READ_ROWS | Maximum number of rows to read in a single request | - | - | 10000 | | MAX_ATTACHMENT_UPLOAD_SIZE | Maximum attachment upload size (bytes) | - | - | 2147483648 | -| MAX_SPACE_OWNER_COUNT | Maximum number of spaces a user can own | 10 | - | 10 | | TASK_MAX_FIELDS_PER_BATCH | Maximum number of AI fields sent to the model in one batch request. Values are clamped from 1 to 20 | 5 | - | 5 | | TASK_MAX_CONCURRENCY | Maximum pending AI field task runs dispatched in one scheduling cycle. Values are clamped from 1 to 20 | 5 | - | 5 | | TABLE_LIMIT_FIELD_OPTIONS_MAX_BYTES | Maximum serialized field option bytes | 262144 | - | 262144 | @@ -105,6 +115,19 @@ For production deployments, use **S3-compatible object storage** (`s3` / `minio` | TABLE_LIMIT_VIEW_OPTIONS_MAX_BYTES | Maximum serialized view option bytes | 262144 | - | 262144 | | TABLE_LIMIT_NAME_MAX_LENGTH | Maximum display name length for supported table objects | 100 | - | 100 | | TABLE_LIMIT_DESCRIPTION_MAX_LENGTH | Maximum description length for supported table objects | 2000 | - | 2000 | +| **AI & App Builder (runtime plane connection)** | | | | | +| SANDBOX_PROVIDER | Sandbox provider for AI sessions; `opensandbox` connects to a self-hosted runtime plane | - | - | opensandbox | +| TEABLE_INFRA_API_URL | Runtime plane entry URL — must be the public entry (it routes `/v1` to the sandbox engine), not an internal service address | - | - | https://infra.teable.example.com | +| TEABLE_INFRA_API_KEY | API key shared with the runtime plane | - | - | your-infra-api-key | +| TEABLE_INFRA_BUCKET | Bucket for the AI workspace data plane | teable-agent | - | teable-agent | +| SANDBOX_OPENSANDBOX_IMAGE | Sandbox agent image **prefix without a tag** — Teable appends its own release tag so the agent always matches the app version. China deployments use the Aliyun mirror prefix | - | - | ghcr.io/teableio/teable-sandbox-agent | +| SANDBOX_OPENSANDBOX_RUNTIME | Sandbox engine runtime type: `kubernetes` or `docker`. Must be `docker` on Docker deployments | kubernetes | - | docker | +| SANDBOX_OPENSANDBOX_USE_SERVER_PROXY | Route sandbox endpoints through the engine's path-based proxy instead of per-port subdomains (used by Docker local mode) | false | - | true | +| APP_DEPLOY_PROVIDER | App Builder deployment backend; full-featured self-host uses `docker-runtime` | vercel | - | docker-runtime | +| SANDBOX_JWT_SECRET | Secret signing sandbox sessions — generate a random value per host | - | - | random string | +| SANDBOX_CPU | vCPUs per sandbox (admin panel settings take precedence) | 2 | - | 2 | +| SANDBOX_MEMORY | Memory (GiB) per sandbox (admin panel settings take precedence) | 4 | - | 4 | +| SANDBOX_DISK | Ephemeral disk (GiB) per sandbox (admin panel settings take precedence) | 15 | - | 15 | | **Feature Toggles** | | | | | | RECORD_HISTORY_DISABLED | Whether to disable record history, defaults to false | false | - | true | | BACKEND_RECORD_HISTORY_COLD_DISABLED | Whether to stop scheduled record-history cold-storage migration, compaction, and buffer deletion. Reads still include migrated history | false | - | true | @@ -118,7 +141,3 @@ For production deployments, use **S3-compatible object storage** (`s3` / `minio` | PRISMA_TRANSACTION_TIMEOUT | Maximum time (ms) a transaction can run before timing out. Increase for long-running transactions (e.g., bulk updates with many foreign keys) | 5000 | - | 60000 | | PRISMA_TRANSACTION_MAX_WAIT | Maximum time (ms) to wait to acquire a transaction from the pool | 2000 | - | 5000 | | BIG_TRANSACTION_TIMEOUT | Timeout (ms) for large internal transactions, such as exporting large bases | 600000 | - | 1200000 | - - -If exporting a large base fails with a Prisma interactive transaction timeout, increase `BIG_TRANSACTION_TIMEOUT` and restart the Teable service. The right value depends on your deployment size, database performance, record count, table complexity, and attachments. Use the smallest value that works reliably for your environment. - diff --git a/zh/deploy/env.mdx b/zh/deploy/env.mdx index 31a4ed57..d737a185 100644 --- a/zh/deploy/env.mdx +++ b/zh/deploy/env.mdx @@ -4,19 +4,27 @@ description: "这里列举了Teable 所有可用的环境变量以及相关解 mode: "wide" --- -{/* mintlify-resync: 2026-05-14 */} - -生产环境请使用 **S3-compatible 对象存储**(`s3` / `minio`),并按 **public/private 双桶**进行配置。同时请确保 public bucket 配置 **public-read + CORS**。详见:[对象存储(S3-compatible)](/zh/deploy/storage)。 - +本页列出的是 **Teable 应用自身**的环境变量。全功能运行平面的各服务(沙箱引擎、 +Infra Service、Git 仓库服务等)由 +[teableio/teable-deployment](https://github.com/teableio/teable-deployment) +中的部署资产单独配置 —— 见 [Agent Runtime 架构](/zh/deploy/agent-runtime)。 + + +如果您使用全功能部署资产(Docker 单机全栈或 Helm chart),下方 **AI 与 App Builder** +组的变量会被自动配置;只有在把已有的基础版 Teable 接入运行平面时才需要手工设置。 + | 环境变量 | 描述 | 默认值 | 必填 | 示例 | | -------------------------------------------- | ------------------------------------------------------- | ---------------- | ---- | ------------------------------------------------ | | **核心配置** | | | | | | PUBLIC_ORIGIN | 用于生成完整 URL 的公共源,必须设置为您的应用程序访问地址 | - | 是 | https://app.teable.ai | | SECRET_KEY | 用于 JWT、会话和共享的密钥,请使用强密码 | defaultSecretKey | 是 | yourStrongSecretKey | +| BACKEND_STORAGE_ENCRYPTION_KEY | 存储令牌加密密钥。默认值是源码中的公开常量 —— 生产部署必须自行设置随机值(全功能部署资产会按主机自动生成) | 公开常量 | - | 16 位随机字符串 | +| BACKEND_STORAGE_ENCRYPTION_IV | 与 BACKEND_STORAGE_ENCRYPTION_KEY 配对的加密 IV | 公开常量 | - | 16 位随机字符串 | +| BACKEND_ACCESS_TOKEN_ENCRYPTION_KEY | 个人访问令牌加密密钥。同样注意:默认值公开,生产环境请自行设置 | 公开常量 | - | 16 位随机字符串 | +| BACKEND_ACCESS_TOKEN_ENCRYPTION_IV | 与 BACKEND_ACCESS_TOKEN_ENCRYPTION_KEY 配对的加密 IV | 公开常量 | - | 16 位随机字符串 | | PORT | 应用程序运行的端口 | 3000 | - | 3000 | | LOG_LEVEL | 日志级别,可选值:fatal、error、warn、info、debug、trace | info | - | debug | -| NEXT_ENV_IMAGES_ALL_REMOTE | 是否允许加载第三方图片 | false | - | true | | **存储配置** | | | | | | BACKEND_STORAGE_PROVIDER | 存储提供商,可选值:local、minio、s3、aliyun | local | - | s3 | | BACKEND_STORAGE_LOCAL_PATH | 本地存储路径 | .assets/uploads | - | .assets/uploads | @@ -79,7 +87,6 @@ mode: "wide" | MAX_COPY_CELLS | 单次请求最大复制单元格数 | - | - | 50000 | | MAX_READ_ROWS | 单次请求最大读取行数 | - | - | 10000 | | MAX_ATTACHMENT_UPLOAD_SIZE | 附件上传最大大小(字节) | - | - | 2147483648 | -| MAX_SPACE_OWNER_COUNT | 单个用户可作为所有者拥有或管理的最大空间数量 | 10 | - | 10 | | TASK_MAX_FIELDS_PER_BATCH | 单次批量请求发送给模型的 AI 字段数量上限,取值会限制在 1 到 20 | 5 | - | 5 | | TASK_MAX_CONCURRENCY | 每轮调度最多派发的待处理 AI 字段任务运行数,取值会限制在 1 到 20 | 5 | - | 5 | | TABLE_LIMIT_FIELD_OPTIONS_MAX_BYTES | 字段选项序列化后的最大字节数 | 262144 | - | 262144 | @@ -105,6 +112,19 @@ mode: "wide" | TABLE_LIMIT_VIEW_OPTIONS_MAX_BYTES | 视图配置序列化后的最大字节数 | 262144 | - | 262144 | | TABLE_LIMIT_NAME_MAX_LENGTH | 支持的表对象显示名称最大长度 | 100 | - | 100 | | TABLE_LIMIT_DESCRIPTION_MAX_LENGTH | 支持的表对象描述最大长度 | 2000 | - | 2000 | +| **AI 与 App Builder(运行平面接入)** | | | | | +| SANDBOX_PROVIDER | AI 会话的沙箱提供方;`opensandbox` 表示接入自部署运行平面 | - | - | opensandbox | +| TEABLE_INFRA_API_URL | 运行平面入口 URL —— 必须是公共入口(它负责把 `/v1` 分流给沙箱引擎),不能填内部服务地址 | - | - | https://infra.teable.example.com | +| TEABLE_INFRA_API_KEY | 与运行平面共享的 API 密钥 | - | - | your-infra-api-key | +| TEABLE_INFRA_BUCKET | AI 工作区数据面使用的存储桶 | teable-agent | - | teable-agent | +| SANDBOX_OPENSANDBOX_IMAGE | 沙箱 agent 镜像**前缀(不带 tag)** —— Teable 会拼上自身发布版本 tag,agent 永远与应用版本配对。中国部署使用阿里云镜像前缀 | - | - | ghcr.io/teableio/teable-sandbox-agent | +| SANDBOX_OPENSANDBOX_RUNTIME | 沙箱引擎运行时类型:`kubernetes` 或 `docker`。Docker 部署必须设为 `docker` | kubernetes | - | docker | +| SANDBOX_OPENSANDBOX_USE_SERVER_PROXY | 沙箱端点走引擎的路径代理而非按端口子域名(Docker local 模式使用) | false | - | true | +| APP_DEPLOY_PROVIDER | App Builder 的部署后端;全功能自部署使用 `docker-runtime` | vercel | - | docker-runtime | +| SANDBOX_JWT_SECRET | 沙箱会话签名密钥 —— 请按主机生成随机值 | - | - | 随机字符串 | +| SANDBOX_CPU | 每个沙箱的 vCPU 数(管理面板中的设置优先) | 2 | - | 2 | +| SANDBOX_MEMORY | 每个沙箱的内存(GiB,管理面板中的设置优先) | 4 | - | 4 | +| SANDBOX_DISK | 每个沙箱的临时磁盘(GiB,管理面板中的设置优先) | 15 | - | 15 | | **功能开关** | | | | | | RECORD_HISTORY_DISABLED | 是否禁用记录历史,默认为 false | false | - | true | | BACKEND_RECORD_HISTORY_COLD_DISABLED | 是否停止记录历史冷存储的定时迁移、合并和数据库缓冲区删除;读取仍会包含已迁移的历史 | false | - | true | @@ -118,7 +138,3 @@ mode: "wide" | PRISMA_TRANSACTION_TIMEOUT | 事务运行超时时间(毫秒),对于长时间运行的事务(如包含多个外键的批量更新)可以增加此值 | 5000 | - | 60000 | | PRISMA_TRANSACTION_MAX_WAIT | 从连接池获取事务的最大等待时间(毫秒) | 2000 | - | 5000 | | BIG_TRANSACTION_TIMEOUT | 大型内部事务的超时时间(毫秒),例如导出大型数据库 | 600000 | - | 1200000 | - - -如果自托管部署导出大型数据库时出现 Prisma interactive transaction timeout,可以调大 `BIG_TRANSACTION_TIMEOUT` 并重启 Teable 服务。合适的值取决于部署规模、数据库性能、记录数量、表复杂度和附件数量。请使用能在当前环境稳定工作的最小值。 - From 40b4694f6caf3868992fdb4939cef19399712de5 Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 19:32:23 +0800 Subject: [PATCH 03/12] docs(self-host): shelve legacy production/config pages as deprecated, keep URLs viewable Per review alignment: - deprecated-banner pattern instead of deletion: content stays reachable at its old URL with a banner pointing to Choose Your Path; pages leave the navigation (k8s and one-key restored from deletion to follow the same pattern; their redirects dropped) - shelved: production-overview, aws, azure, gcp (Production Deployment group removed), storage (bucket layout is now managed by the deployment assets; full-featured uses three buckets), database-connection, k8s, one-key - navigation: Quick Start = choose -> docker -> agent-runtime (architecture) - docker page: retargeted/removed links into shelved pages - en + zh in sync Co-Authored-By: Claude Fable 5 --- docs.json | 46 +--- en/deploy/aws.mdx | 7 + en/deploy/azure.mdx | 7 + en/deploy/database-connection.mdx | 7 + en/deploy/docker.mdx | 8 +- en/deploy/env.mdx | 7 - en/deploy/gcp.mdx | 7 + en/deploy/k8s.mdx | 433 +++++++++++++++++++++++++++++ en/deploy/one-key.mdx | 48 ++++ en/deploy/production-overview.mdx | 7 + en/deploy/storage.mdx | 7 + zh/deploy/aws.mdx | 6 + zh/deploy/azure.mdx | 6 + zh/deploy/database-connection.mdx | 6 + zh/deploy/docker.mdx | 6 +- zh/deploy/env.mdx | 5 - zh/deploy/gcp.mdx | 6 + zh/deploy/k8s.mdx | 435 ++++++++++++++++++++++++++++++ zh/deploy/one-key.mdx | 42 +++ zh/deploy/production-overview.mdx | 6 + zh/deploy/storage.mdx | 6 + 21 files changed, 1043 insertions(+), 65 deletions(-) create mode 100644 en/deploy/k8s.mdx create mode 100644 en/deploy/one-key.mdx create mode 100644 zh/deploy/k8s.mdx create mode 100644 zh/deploy/one-key.mdx diff --git a/docs.json b/docs.json index 8c5c8054..37d72148 100644 --- a/docs.json +++ b/docs.json @@ -35,22 +35,6 @@ { "source": "/zh/about", "destination": "/zh/basic/ai/overview" - }, - { - "source": "/en/deploy/k8s", - "destination": "/en/deploy/agent-runtime" - }, - { - "source": "/zh/deploy/k8s", - "destination": "/zh/deploy/agent-runtime" - }, - { - "source": "/en/deploy/one-key", - "destination": "/en/deploy/choose" - }, - { - "source": "/zh/deploy/one-key", - "destination": "/zh/deploy/choose" } ], "navigation": { @@ -312,7 +296,8 @@ "group": "Quick Start", "pages": [ "en/deploy/choose", - "en/deploy/docker" + "en/deploy/docker", + "en/deploy/agent-runtime" ] }, { @@ -331,23 +316,11 @@ "en/basic/admin-panel/multitenancy" ] }, - { - "group": "Production Deployment", - "pages": [ - "en/deploy/production-overview", - "en/deploy/agent-runtime", - "en/deploy/aws", - "en/deploy/azure", - "en/deploy/gcp" - ] - }, { "group": "Configuration", "pages": [ "en/deploy/env", - "en/deploy/storage", "en/deploy/email", - "en/deploy/database-connection", "en/deploy/oidc", "en/deploy/nginx", "en/deploy/dashboard" @@ -675,7 +648,8 @@ "group": "快速开始", "pages": [ "zh/deploy/choose", - "zh/deploy/docker" + "zh/deploy/docker", + "zh/deploy/agent-runtime" ] }, { @@ -694,23 +668,11 @@ "zh/basic/admin-panel/multitenancy" ] }, - { - "group": "生产环境部署", - "pages": [ - "zh/deploy/production-overview", - "zh/deploy/agent-runtime", - "zh/deploy/aws", - "zh/deploy/azure", - "zh/deploy/gcp" - ] - }, { "group": "配置", "pages": [ "zh/deploy/env", - "zh/deploy/storage", "zh/deploy/email", - "zh/deploy/database-connection", "zh/deploy/oidc", "zh/deploy/nginx", "zh/deploy/dashboard" diff --git a/en/deploy/aws.mdx b/en/deploy/aws.mdx index f0011692..1cd55aae 100644 --- a/en/deploy/aws.mdx +++ b/en/deploy/aws.mdx @@ -3,6 +3,13 @@ title: "Deploy on AWS" description: "Step-by-step guide: Deploy Teable on AWS using ECS, RDS PostgreSQL, ElastiCache, and S3" --- + +**Deprecated.** This page is no longer maintained and has been removed from the +docs navigation. For current deployment guidance, start from +[Choose Your Path](/en/deploy/choose). + + + **Recommended for:** Production deployments with 50+ users diff --git a/en/deploy/azure.mdx b/en/deploy/azure.mdx index 8510dba1..cd04f1f2 100644 --- a/en/deploy/azure.mdx +++ b/en/deploy/azure.mdx @@ -3,6 +3,13 @@ title: "Deploy on Azure" description: "Step-by-step guide: Deploy Teable on Azure using App Service or AKS, with PostgreSQL Flexible Server, Azure Cache for Redis, and S3-compatible storage" --- + +**Deprecated.** This page is no longer maintained and has been removed from the +docs navigation. For current deployment guidance, start from +[Choose Your Path](/en/deploy/choose). + + + **Recommended for:** Production deployments with 50+ users diff --git a/en/deploy/database-connection.mdx b/en/deploy/database-connection.mdx index 3c469c56..2c7e1f24 100644 --- a/en/deploy/database-connection.mdx +++ b/en/deploy/database-connection.mdx @@ -3,6 +3,13 @@ title: "Enable External Database Connection" description: "Automatically create database roles in the application to enable secure access to base data from external applications" --- + +**Deprecated.** This page is no longer maintained and has been removed from the +docs navigation. For current deployment guidance, start from +[Choose Your Path](/en/deploy/choose). + + + External database connection overview **Docker Compose is recommended for small teams and evaluation** (< 50 users). -For production deployments, first read the [Overview](/en/deploy/production-overview) or a cloud-specific guide. Need AI features or App Builder? See the [full-featured platform](/en/deploy/agent-runtime) instead. +Not sure this is the right path? Start from [Choose Your Path](/en/deploy/choose). Need AI features or App Builder? Deploy the [full-featured platform](/en/deploy/agent-runtime) instead. ## Docker Compose Deployment @@ -170,8 +170,6 @@ BACKEND_CACHE_REDIS_URI=redis://default:${REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_ For production deployments, use **S3-compatible object storage** (such as S3, MinIO, OSS/OBS/COS with S3-compatible endpoints, or another S3-compatible service) for better durability and scaling. -See: [Object Storage (S3-compatible)](/en/deploy/storage) - MinIO provides an additional storage management interface (port 9001) and more powerful, stable file service. @@ -329,7 +327,7 @@ STORAGE_PREFIX=https://your-public-bucket.s3.us-west-2.amazonaws.com ``` -This process applies to most cloud providers, not just AWS S3. For exact bucket policy and CORS examples, see: [Object Storage (S3-compatible)](/en/deploy/storage) +This process applies to most cloud providers, not just AWS S3. @@ -382,8 +380,6 @@ Note that `127.0.0.1` is the container's internal network. If you want to connec [Configure Email Service](/en/deploy/email) -[Configure External Database Connection](/en/deploy/database-connection) - ## Support and Feedback If you encounter any issues during deployment, please contact our support team at support@teable.ai or [submit an issue](https://github.com/teableio/teable/issues). diff --git a/en/deploy/env.mdx b/en/deploy/env.mdx index 4c9b8e2c..fec2ba84 100644 --- a/en/deploy/env.mdx +++ b/en/deploy/env.mdx @@ -10,13 +10,6 @@ are configured separately by the deployment assets in [teableio/teable-deployment](https://github.com/teableio/teable-deployment) — see [Agent Runtime Architecture](/en/deploy/agent-runtime). - -If you deploy with the full-featured assets (Docker all-in-one or the Helm -chart), the **AI & App Builder** variables below are set for you automatically. -Set them by hand only when attaching an existing standalone Teable to a -runtime plane. - - | Environment Variable | Description | Default Value | Required | Example | | ----------------------------------------- | -------------------------------------------------------------------------- | --------------- | -------- | ------------------------------------------------- | | **Core Configuration** | | | | | diff --git a/en/deploy/gcp.mdx b/en/deploy/gcp.mdx index 3d3f073a..6e4d34ee 100644 --- a/en/deploy/gcp.mdx +++ b/en/deploy/gcp.mdx @@ -3,6 +3,13 @@ title: "Deploy on GCP" description: "Step-by-step guide: Deploy Teable on Google Cloud Platform using Cloud Run/GKE, Cloud SQL, Memorystore, and S3-compatible storage" --- + +**Deprecated.** This page is no longer maintained and has been removed from the +docs navigation. For current deployment guidance, start from +[Choose Your Path](/en/deploy/choose). + + + **Recommended for:** Production deployments with 50+ users diff --git a/en/deploy/k8s.mdx b/en/deploy/k8s.mdx new file mode 100644 index 00000000..cbc5912e --- /dev/null +++ b/en/deploy/k8s.mdx @@ -0,0 +1,433 @@ +--- +title: "Kubernetes Deployment" +--- + + +**Deprecated.** This page is no longer maintained and has been removed from the +docs navigation. For current deployment guidance, start from +[Choose Your Path](/en/deploy/choose). Kubernetes deployments now use the full-featured Helm chart — see [Agent Runtime Architecture](/en/deploy/agent-runtime). + + + + +**Recommended for:** Production deployments with 50+ users + + + +For resource sizing recommendations (CPU, memory, replicas), see [Self-Hosted Overview → Resource Recommendations](/en/deploy/production-overview#resource-recommendations). + + +## Prerequisites +### Teable Images +- Application image: `ghcr.io/teableio/teable:latest` + +### Runtime Environment +- Running Kubernetes cluster + +### Required External Services +#### Database Service +- PostgreSQL database: postgres:15.4 (version > 12) + - PG image: `postgres:15.4` + +#### Cache Service +- Redis cache service: redis:7.2.4 (version > 5) + - Redis image: `redis:7.2.4` + +#### Object Storage Service +- MinIO object storage service: `minio/minio:RELEASE.2025-04-22T22-12-26Z` + +### Required Tools +- kubectl command-line tool + +## Dependency Component Configuration +### File Storage (MinIO) +File storage service must be accessible from the public internet (directly accessible by end users) + +Two buckets need to be created in advance for file storage: + +| Bucket | Environment Variable | Permission | +|--------|---------------------|------------| +| Public bucket | `BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub` | **Public read (anonymous access required)** | +| Private bucket | `BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt` | Private (default) | + + +The public bucket **must** have anonymous read access enabled. Without this, users cannot view shared images, avatars, and other public files. + + +#### Create Buckets Using MinIO Client (mc) + +Before deploying Teable, create the required buckets: + +```sh +# Set up MinIO client alias +mc alias set teable-minio https://minio.example.com YOUR_ACCESS_KEY YOUR_SECRET_KEY + +# Create public bucket and set anonymous read access +mc mb --ignore-existing teable-minio/teable-pub +mc anonymous set public teable-minio/teable-pub + +# Create private bucket +mc mb --ignore-existing teable-minio/teable-pvt + +# Verify buckets exist +mc ls teable-minio +# Expected output: +# [2024-01-01 00:00:00 UTC] 0B teable-pub/ +# [2024-01-01 00:00:00 UTC] 0B teable-pvt/ +``` + +#### Teable MinIO Environment Variables Overview + +```sh +# Fixed value +BACKEND_STORAGE_PROVIDER=minio +# Public bucket +BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub +# Private bucket +BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt +# Public endpoint, important! Must be accessible by end users +BACKEND_STORAGE_MINIO_ENDPOINT=minio.example.com +# Same as above but with protocol +STORAGE_PREFIX=https://minio.example.com +# Internal network endpoint (Kubernetes DNS format: ..svc.cluster.local) +# ⚠️ Replace with your actual namespace, e.g., minio.teable.svc.cluster.local +BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT=minio.teable.svc.cluster.local +# Public port, typically 443 or 9000 +BACKEND_STORAGE_MINIO_PORT=443 +# Internal network port, typically 80 or 9000 +BACKEND_STORAGE_MINIO_INTERNAL_PORT=80 +# Enable HTTPS, note: if Teable uses HTTPS, MinIO must also use HTTPS to avoid CORS issues +BACKEND_STORAGE_MINIO_USE_SSL="true" +# Admin account +BACKEND_STORAGE_MINIO_ACCESS_KEY=root +# Admin password +BACKEND_STORAGE_MINIO_SECRET_KEY=rootPassword +``` + +### Database +Create a database account with administrative privileges, a database, and set a password. +Environment variables example: + - Database name: teable + - Password: your-password + - Username: postgres + - Port: 5432 +```sh +PRISMA_DATABASE_URL="postgresql://postgres:your-password@your-postgres-host:5432/teable" +``` + +### Redis Cache +Teable only needs the internal network address for Redis cache configuration. (Note: Redis manages both cache and queues, it's essential. Data should be backed up regularly) +Environment variables example: +```sh +BACKEND_CACHE_REDIS_URI="redis://username:password@your-redis-host:6379/0" +``` + +## Create Configuration Files + +```yaml teable-config.yaml (Non-sensitive configuration) +apiVersion: v1 +kind: ConfigMap +metadata: + name: teable-config + namespace: teable # Replace with your namespace +data: + # Application base configuration, public access domain + PUBLIC_ORIGIN: "https://your-domain.com" + + # Storage configuration + BACKEND_STORAGE_PROVIDER: "minio" + # Bucket names (must be created in MinIO before deployment) + BACKEND_STORAGE_PUBLIC_BUCKET: "teable-pub" + BACKEND_STORAGE_PRIVATE_BUCKET: "teable-pvt" + # Public endpoint, important! Must be accessible by end users + BACKEND_STORAGE_MINIO_ENDPOINT: "minio.example.com" + # Same as above but with protocol + STORAGE_PREFIX: "https://minio.example.com" + # Internal endpoint (Kubernetes DNS format: ..svc.cluster.local) + # ⚠️ Replace 'teable' with your actual namespace + BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT: "minio.teable.svc.cluster.local" + # Public port, typically 443 or 9000 + BACKEND_STORAGE_MINIO_PORT: "443" + # Internal port, typically 80 or 9000 + BACKEND_STORAGE_MINIO_INTERNAL_PORT: "80" + # Enable HTTPS, note: if Teable uses HTTPS, MinIO must also use HTTPS to avoid CORS issues + BACKEND_STORAGE_MINIO_USE_SSL: "true" + + # Cache configuration, fixed value + BACKEND_CACHE_PROVIDER: "redis" + + # Other configurations, fixed values + NEXT_ENV_IMAGES_ALL_REMOTE: "true" + PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" + # Keep this when using self-signed certificates + NODE_TLS_REJECT_UNAUTHORIZED: '0' +``` + +```yaml teable-secrets.yaml (Sensitive information) +apiVersion: v1 +kind: Secret +metadata: + name: teable-secrets + namespace: teable # Replace with your namespace +type: Opaque +stringData: + # Database sensitive information + PRISMA_DATABASE_URL: "postgresql://postgres:your-password@your-postgres-host:5432/teable" + + # Application secrets + BACKEND_JWT_SECRET: "your-jwt-secret" + BACKEND_SESSION_SECRET: "your-session-secret" + + # MinIO authentication + BACKEND_STORAGE_MINIO_ACCESS_KEY: "your-minio-access-key" + BACKEND_STORAGE_MINIO_SECRET_KEY: "your-minio-secret-key" + + # Redis authentication + BACKEND_CACHE_REDIS_URI: "redis://username:password@your-redis-host:6379/0" +``` + +```yaml teable-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: teable + namespace: teable # Replace with your namespace +spec: + replicas: 1 # Configure as needed + selector: + matchLabels: + app: teable + template: + metadata: + labels: + app: teable + spec: + # Add initContainers for database migration + initContainers: + - name: db-migrate + image: ghcr.io/teableio/teable:latest + args: + - migrate-only + envFrom: + - configMapRef: + name: teable-config + - secretRef: + name: teable-secrets + resources: + requests: + cpu: 100m + memory: 102Mi + limits: + cpu: 1000m + memory: 1024Mi + containers: + - name: teable + image: ghcr.io/teableio/teable:latest + args: + - skip-migrate + ports: + - containerPort: 3000 + envFrom: + - configMapRef: + name: teable-config + - secretRef: + name: teable-secrets + resources: + requests: + cpu: 200m + memory: 400Mi + limits: + cpu: 2000m + memory: 4096Mi + startupProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 30 + successThreshold: 1 + livenessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + readinessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 +``` + +```yaml teable-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: teable + namespace: teable # Replace with your namespace +spec: + ports: + - port: 3000 + targetPort: 3000 + selector: + app: teable +``` + +```yaml teable-ingress.yaml (Optional) +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: teable + namespace: teable # Replace with your namespace + # Example using nginx, if using other ingress class, please replace + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "100m" + nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" + nginx.ingress.kubernetes.io/proxy-send-timeout: "600" + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + nginx.ingress.kubernetes.io/proxy-request-buffering: "off" +spec: + ingressClassName: nginx # Change to your ingress class if different + rules: + - host: your-domain.com # Replace with your domain + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: teable + port: + number: 3000 + tls: + - hosts: + - your-domain.com # Replace with your domain + secretName: your-tls-secret +``` + + +Do **NOT** add `nginx.ingress.kubernetes.io/rewrite-target` annotation. This will break Teable's internal routing. + + + +## Deployment Steps + + +Before deploying Teable, ensure that: +1. MinIO buckets (`teable-pub` and `teable-pvt`) are created +2. Public bucket has anonymous read access enabled +3. PostgreSQL database `teable` exists +4. Redis is accessible + + +1. Create namespace (if not exists): +```sh +kubectl create namespace teable +``` + +2. Create configuration and secrets: +```sh +kubectl apply -f teable-config.yaml +kubectl apply -f teable-secrets.yaml +``` + +3. Deploy application: +```sh +kubectl apply -f teable-deployment.yaml +kubectl apply -f teable-service.yaml +kubectl apply -f teable-ingress.yaml # Optional, if using Ingress +``` + +4. Verify deployment: +```sh +# Check Pod status +kubectl get pods -n teable -l app=teable + +# Wait for pods to be ready +kubectl rollout status deployment/teable -n teable + +# View application logs +kubectl logs -n teable -l app=teable +``` + +### Configuration Notes + +1. Sensitive Information Management + - All password, secret-related information should be managed through Secrets + +2. Database Configuration + - Ensure that the teable database exists in the database + - The database user needs to have appropriate permissions + - It's recommended to use a connection pool to manage database connections + +3. MinIO Configuration + - Ensure that the storage bucket exists and the permissions are correct, one public bucket, one private bucket + - The public bucket needs to be completely publicly readable + - The internal and external access address configuration is correct + +4. Redis Configuration + - It's recommended to enable Redis persistence + - Configure appropriate memory limits + - Consider using Redis cluster to improve availability + +5. Security Recommendations + - Use strong passwords and secrets + - Enable TLS/SSL encryption + - Regularly update certificates + - Limit network access scope + +6. Resource Configuration + - Adjust resource limits based on actual load + - Monitor resource usage + - Configure appropriate health check parameters + +### Troubleshooting + +#### Common Issues + +| Error | Cause | Solution | +|-------|-------|----------| +| `ECONNREFUSED` to MinIO | MinIO internal endpoint incorrect or unreachable | Verify `BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT` uses correct format: `..svc.cluster.local` | +| `Bucket not found` | MinIO buckets not created | Create buckets using `mc mb` command before deployment | +| `Access Denied` on file upload | MinIO credentials incorrect | Verify `BACKEND_STORAGE_MINIO_ACCESS_KEY` and `SECRET_KEY` | +| `Page not found` on web access | Ingress `rewrite-target` configured | Remove `rewrite-target` annotation from Ingress | +| Pod stuck in `CrashLoopBackOff` | Backend failed to start | Check logs for MinIO, database, or Redis connectivity issues | + +#### Diagnostic Commands + +1. Check Pod status: +```sh +kubectl describe pod -n teable -l app=teable +``` + +2. View application logs: +```sh +kubectl logs -n teable -l app=teable +kubectl logs -n teable -l app=teable --previous # If pod crashed +``` + +3. Verify configuration: +```sh +kubectl describe configmap teable-config -n teable +kubectl describe secret teable-secrets -n teable +``` + +4. Check network connection: +```sh +kubectl exec -it -n teable -- curl -v localhost:3000/health +``` + +5. Test MinIO connectivity from inside the cluster: +```sh +kubectl run test-minio --rm -it --image=curlimages/curl --restart=Never -n teable -- \ + curl -v http://minio.teable.svc.cluster.local/minio/health/live +``` diff --git a/en/deploy/one-key.mdx b/en/deploy/one-key.mdx new file mode 100644 index 00000000..534485e3 --- /dev/null +++ b/en/deploy/one-key.mdx @@ -0,0 +1,48 @@ +--- +title: "One-Click Cloud Deployment" +description: "Deploy Teable quickly using cloud provider templates, then subscribe to unlock paid features and activate your license." +--- + + +**Deprecated.** This page is no longer maintained and has been removed from the +docs navigation. For current deployment guidance, start from +[Choose Your Path](/en/deploy/choose). The templates below are currently not functional. + + + +## One-Click Cloud Deployment + +One-click templates help you bootstrap a Teable environment quickly. After deployment, if you want paid features (for self-hosted plans), you should **subscribe** and **activate your license** using your Instance ID. + +## Step 1: Deploy with a template + +Choose a template below and deploy. + +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/NtH5uD?referralCode=rE4BjB) + +[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/QF8695) + +[![Deploy to RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploylobe.svg)](https://repocloud.io/details/?app_id=273) + +[![Deploy on Elestio](https://elest.io/images/logos/deploy-to-elestio-btn.png)](https://elest.io/open-source/teable) + +## Step 2: Subscribe (paid features) + +If you are deploying **self-hosted** and need paid features, subscribe with your **Instance ID**: + +1. Open the Admin Panel in your deployed instance and copy the **Instance ID** +2. Go to the self-hosted pricing page and subscribe: `https://app.teable.ai/public/pricing?host=self-hosted` +3. After subscribing, you will get a **License Key** + +Then activate it in your instance: + +- Documentation: [Subscribe and Activate License](/en/deploy/activate) + + +If you are using **Teable Cloud** (hosted by Teable), manage your plan in-app: +see [Billing & Subscription](/en/basic/space/billing). + + +## Need help? + +If you have questions about subscription or licensing, contact `support@teable.ai`. diff --git a/en/deploy/production-overview.mdx b/en/deploy/production-overview.mdx index 49f68366..ba9a0cde 100644 --- a/en/deploy/production-overview.mdx +++ b/en/deploy/production-overview.mdx @@ -3,6 +3,13 @@ title: "Overview" description: "Architecture, sizing, and deployment path selection for production Teable deployments" --- + +**Deprecated.** This page is no longer maintained and has been removed from the +docs navigation. For current deployment guidance, start from +[Choose Your Path](/en/deploy/choose). + + + **Recommended for:** Production deployments with 50+ users diff --git a/en/deploy/storage.mdx b/en/deploy/storage.mdx index 9a036989..9e9c9312 100644 --- a/en/deploy/storage.mdx +++ b/en/deploy/storage.mdx @@ -3,6 +3,13 @@ title: "Object Storage (S3-compatible)" description: "Production storage guidance for Teable: public/private buckets, permissions, and CORS" --- + +**Deprecated.** This page is no longer maintained and has been removed from the +docs navigation. For current deployment guidance, start from +[Choose Your Path](/en/deploy/choose). Storage now ships preconfigured with the deployment assets; the full-featured platform manages three buckets (public, private, and build artifacts) for you. + + + For production deployments, use **S3-compatible object storage** (such as S3, MinIO, OSS/OBS/COS with S3-compatible endpoints, or another S3-compatible service) with **public/private dual buckets**. diff --git a/zh/deploy/aws.mdx b/zh/deploy/aws.mdx index b5134e6f..1fc44923 100644 --- a/zh/deploy/aws.mdx +++ b/zh/deploy/aws.mdx @@ -3,6 +3,12 @@ title: "在 AWS 上部署" description: "分步指引:在 AWS 上使用 ECS、RDS PostgreSQL、ElastiCache 与 S3 部署 Teable" --- + +**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 +[选择部署方式](/zh/deploy/choose)开始。 + + + **推荐场景**:50+ 用户的生产环境 diff --git a/zh/deploy/azure.mdx b/zh/deploy/azure.mdx index b3f09892..61fc6020 100644 --- a/zh/deploy/azure.mdx +++ b/zh/deploy/azure.mdx @@ -3,6 +3,12 @@ title: "在 Azure 上部署" description: "分步指引:在 Azure 上使用 App Service 或 AKS 部署 Teable,搭配 PostgreSQL Flexible Server、Azure Cache for Redis 与 S3-compatible 存储" --- + +**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 +[选择部署方式](/zh/deploy/choose)开始。 + + + **推荐场景**:50+ 用户的生产环境 diff --git a/zh/deploy/database-connection.mdx b/zh/deploy/database-connection.mdx index bd5c4bb3..8c50caf4 100644 --- a/zh/deploy/database-connection.mdx +++ b/zh/deploy/database-connection.mdx @@ -3,6 +3,12 @@ title: "启用外部数据库连接" description: "在应用中自动创建数据库角色允许外部应用对表格数据的安全访问功能" --- + +**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 +[选择部署方式](/zh/deploy/choose)开始。 + + + 外部数据库连接概览 **Docker Compose 适合小团队和评估测试**(50 人以内)。 -生产环境部署请先阅读 [概览](/zh/deploy/production-overview) 或云服务部署方案。需要 AI 能力或 App Builder?请改用[全功能平台](/zh/deploy/agent-runtime)。 +不确定该选哪条路径?请从[选择部署方式](/zh/deploy/choose)开始。需要 AI 能力或 App Builder?请改用[全功能平台](/zh/deploy/agent-runtime)。 ## Docker Compose 部署 @@ -177,8 +177,6 @@ BACKEND_CACHE_REDIS_URI=redis://default:${REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_ 生产环境请使用 **S3-compatible 对象存储**(如 S3、MinIO、兼容 S3 的 OSS/OBS/COS 等),以获得更好的可靠性与扩展能力。 -详见:[对象存储(S3-compatible)](/zh/deploy/storage) - minio 提供了额外的存储管理界面(9001 端口)以及更强大的稳定的文件服务 @@ -448,8 +446,6 @@ POSTGRES_USER=teable [配置邮件服务](/zh/deploy/email) -[配置外部数据库连接](/zh/deploy/database-connection) - ## 支持与反馈 如果您在部署过程中遇到任何问题,请联系我们的支持团队 support\@teable.ai 或[提交 issue](https://github.com/teableio/teable/issues)。 diff --git a/zh/deploy/env.mdx b/zh/deploy/env.mdx index d737a185..d5d42b1c 100644 --- a/zh/deploy/env.mdx +++ b/zh/deploy/env.mdx @@ -9,11 +9,6 @@ Infra Service、Git 仓库服务等)由 [teableio/teable-deployment](https://github.com/teableio/teable-deployment) 中的部署资产单独配置 —— 见 [Agent Runtime 架构](/zh/deploy/agent-runtime)。 - -如果您使用全功能部署资产(Docker 单机全栈或 Helm chart),下方 **AI 与 App Builder** -组的变量会被自动配置;只有在把已有的基础版 Teable 接入运行平面时才需要手工设置。 - - | 环境变量 | 描述 | 默认值 | 必填 | 示例 | | -------------------------------------------- | ------------------------------------------------------- | ---------------- | ---- | ------------------------------------------------ | | **核心配置** | | | | | diff --git a/zh/deploy/gcp.mdx b/zh/deploy/gcp.mdx index f0d82d91..d1e12c5f 100644 --- a/zh/deploy/gcp.mdx +++ b/zh/deploy/gcp.mdx @@ -3,6 +3,12 @@ title: "在 GCP 上部署" description: "分步指引:在 Google Cloud Platform 上使用 Cloud Run/GKE、Cloud SQL、Memorystore 与 S3-compatible 存储部署 Teable" --- + +**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 +[选择部署方式](/zh/deploy/choose)开始。 + + + **推荐场景**:50+ 用户的生产环境 diff --git a/zh/deploy/k8s.mdx b/zh/deploy/k8s.mdx new file mode 100644 index 00000000..b62a45b9 --- /dev/null +++ b/zh/deploy/k8s.mdx @@ -0,0 +1,435 @@ +--- +title: "K8s 部署" +--- + + +**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 +[选择部署方式](/zh/deploy/choose)开始。 Kubernetes 部署现统一使用全功能 Helm chart —— 见 [Agent Runtime 架构](/zh/deploy/agent-runtime)。 + + + + +**推荐场景**:50+ 用户的生产环境 + + + +配置建议(CPU、内存、副本数)请参考 [私有化部署概览 → 配置建议](/zh/deploy/production-overview#配置建议)。 + + +## 前置条件 +### Teable 镜像 +- 应用镜像: `registry.cn-shenzhen.aliyuncs.com/teable/teable:latest` + +### 运行环境 +- 运行中的 Kubernetes 集群 + +### 已配置的外部服务 +#### 数据库服务 +- PostgreSQL 数据库: postgres:15.4(版本号大于 12) + - PostgreSQL 镜像: `registry.cn-shenzhen.aliyuncs.com/teable/postgres:15.4` + +#### 缓存服务 +- Redis 缓存服务: redis:7.2.4(版本号大于 5) + - Redis 镜像: `registry.cn-shenzhen.aliyuncs.com/teable/redis:7.2.4` + +#### 对象存储服务 +- MinIO 对象存储服务: minio:RELEASE.2024-10-13T13-34-11Z-cpuv1 + - MinIO 镜像: `registry.cn-shenzhen.aliyuncs.com/teable/minio:RELEASE.2024-10-13T13-34-11Z-cpuv1` + - 注意: 可以直接使用最新版本,或者使用云存储,配置参考,下方示例使用 minio 来做例子。 + +### 必要工具 +- kubectl 命令行工具 + +## 依赖组件配置 +### 文件存储 MinIO +文件存储服务需要支持外网访问(即终端用户可直接访问到) + +文件存储需要预先创建好两个 bucket: + +| Bucket | 环境变量 | 权限 | +|--------|---------|------| +| 公共桶 | `BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub` | **公共可读(必须开启匿名访问)** | +| 私有桶 | `BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt` | 私有(默认) | + + +公共桶**必须**开启匿名读取权限。否则用户将无法查看共享图片、头像等公开文件。 + + +#### 使用 MinIO Client (mc) 创建 Bucket + +在部署 Teable 之前,请先创建所需的 bucket: + +```sh +# 设置 MinIO 客户端别名 +mc alias set teable-minio https://minio.example.com YOUR_ACCESS_KEY YOUR_SECRET_KEY + +# 创建公共桶并设置匿名读取权限 +mc mb --ignore-existing teable-minio/teable-pub +mc anonymous set public teable-minio/teable-pub + +# 创建私有桶 +mc mb --ignore-existing teable-minio/teable-pvt + +# 验证 bucket 是否创建成功 +mc ls teable-minio +# 预期输出: +# [2024-01-01 00:00:00 UTC] 0B teable-pub/ +# [2024-01-01 00:00:00 UTC] 0B teable-pvt/ +``` + +#### Teable MinIO 环境变量总览 + +```sh +# 固定值 +BACKEND_STORAGE_PROVIDER=minio +# 公共桶 +BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub +# 私有桶 +BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt +# 外网端点,重要!需要终端用户可以直接访问到 +BACKEND_STORAGE_MINIO_ENDPOINT=minio.example.com +# 与前面的值保持一样,但是要加个协议名称 +STORAGE_PREFIX=https://minio.example.com +# 内网端点(Kubernetes DNS 格式:..svc.cluster.local) +# ⚠️ 请将 替换为实际的命名空间,例如:minio.teable.svc.cluster.local +BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT=minio.teable.svc.cluster.local +# 外网端口,一般为 443 或者 9000 +BACKEND_STORAGE_MINIO_PORT=443 +# 内网端口,一般为 80 或者 9000 +BACKEND_STORAGE_MINIO_INTERNAL_PORT=80 +# 是否启用 HTTPS, 注意如果 Teable 启用了 https 则 minio 也必须启用 https 否则有跨域问题 +BACKEND_STORAGE_MINIO_USE_SSL="true" +# 管理员账号 +BACKEND_STORAGE_MINIO_ACCESS_KEY=root +# 管理员密码 +BACKEND_STORAGE_MINIO_SECRET_KEY=rootPassword +``` + +### 数据库 +需要创建一个具有管理员权限的数据库账户,以及一个数据库,并且设置密码。 +对应环境变量示例: + - 数据库名称: teable + - 密码:your-password + - 账号: postgres + - 端口: 5432 +```sh +PRISMA_DATABASE_URL="postgresql://postgres:your-password@your-postgres-host:5432/teable" +``` + +### 缓存 Redis +Teable 配置 redis 缓存只需要提供内网连接地址即可。(注意 Redis 不仅管理缓存还管理队列,不可或缺。数据需要定时备份) +对应环境变量示例 +```sh +BACKEND_CACHE_REDIS_URI="redis://username:password@your-redis-host:6379/0" +``` + +## 创建配置文件 + +```yaml teable-config.yaml(非敏感配置) +apiVersion: v1 +kind: ConfigMap +metadata: + name: teable-config + namespace: teable # 请替换为你的命名空间 +data: + # 应用基础配置,对外访问的域名 + PUBLIC_ORIGIN: "https://your-domain.com" + + # 存储配置 + BACKEND_STORAGE_PROVIDER: "minio" + # Bucket 名称(需要在部署前于 MinIO 中创建) + BACKEND_STORAGE_PUBLIC_BUCKET: "teable-pub" + BACKEND_STORAGE_PRIVATE_BUCKET: "teable-pvt" + # 外网端点,重要!需要终端用户可以直接访问到 + BACKEND_STORAGE_MINIO_ENDPOINT: "minio.example.com" + # 与前面的值保持一样,但是要加个协议名称 + STORAGE_PREFIX: "https://minio.example.com" + # 内网端点(Kubernetes DNS 格式:..svc.cluster.local) + # ⚠️ 请将 'teable' 替换为实际的命名空间 + BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT: "minio.teable.svc.cluster.local" + # 外网端口,一般为 443 或者 9000 + BACKEND_STORAGE_MINIO_PORT: "443" + # 内网端口,一般为 80 或者 9000 + BACKEND_STORAGE_MINIO_INTERNAL_PORT: "80" + # 是否启用 HTTPS, 注意如果 Teable 启用了 https 则 minio 也必须启用 https 否则有跨域问题 + BACKEND_STORAGE_MINIO_USE_SSL: "true" + + # 缓存配置, 固定值 + BACKEND_CACHE_PROVIDER: "redis" + + # 其他配置,固定值 + NEXT_ENV_IMAGES_ALL_REMOTE: "true" + PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" + # 使用自签发证书需要保留这个 + NODE_TLS_REJECT_UNAUTHORIZED: '0' +``` + +```yaml teable-secrets.yaml(敏感信息) +apiVersion: v1 +kind: Secret +metadata: + name: teable-secrets + namespace: teable # 请替换为你的命名空间 +type: Opaque +stringData: + # 数据库敏感信息 + PRISMA_DATABASE_URL: "postgresql://postgres:your-password@your-postgres-host:5432/teable" + + # 应用密钥 + BACKEND_JWT_SECRET: "your-jwt-secret" + BACKEND_SESSION_SECRET: "your-session-secret" + + # MinIO 认证信息 + BACKEND_STORAGE_MINIO_ACCESS_KEY: "your-minio-access-key" + BACKEND_STORAGE_MINIO_SECRET_KEY: "your-minio-secret-key" + + # Redis 认证信息 + BACKEND_CACHE_REDIS_URI: "redis://username:password@your-redis-host:6379/0" +``` + + +```yaml teable-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: teable + namespace: teable # 请替换为你的命名空间 +spec: + replicas: 1 # 根据需要配置 + selector: + matchLabels: + app: teable + template: + metadata: + labels: + app: teable + spec: + # Add initContainers for database migration + initContainers: + - name: db-migrate + image: registry.cn-shenzhen.aliyuncs.com/teable/teable:latest + args: + - migrate-only + envFrom: + - configMapRef: + name: teable-config + - secretRef: + name: teable-secrets + resources: + requests: + cpu: 100m + memory: 102Mi + limits: + cpu: 1000m + memory: 1024Mi + containers: + - name: teable + image: registry.cn-shenzhen.aliyuncs.com/teable/teable:latest + args: + - skip-migrate + ports: + - containerPort: 3000 + envFrom: + - configMapRef: + name: teable-config + - secretRef: + name: teable-secrets + resources: + requests: + cpu: 200m + memory: 400Mi + limits: + cpu: 2000m + memory: 4096Mi + startupProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 30 + successThreshold: 1 + livenessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + readinessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 +``` + +```yaml teable-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: teable + namespace: teable # 请替换为你的命名空间 +spec: + ports: + - port: 3000 + targetPort: 3000 + selector: + app: teable +``` + +```yaml teable-ingress.yaml (可选) +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: teable + namespace: teable # 请替换为你的命名空间 + # 以 nginx 做示例,如果使用其他的 ingress class 请替换 + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "100m" + nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" + nginx.ingress.kubernetes.io/proxy-send-timeout: "600" + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + nginx.ingress.kubernetes.io/proxy-request-buffering: "off" +spec: + ingressClassName: nginx # 如果使用其他 ingress class 请修改 + rules: + - host: your-domain.com # 请替换为你的域名 + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: teable + port: + number: 3000 + tls: + - hosts: + - your-domain.com # 请替换为你的域名 + secretName: your-tls-secret +``` + + +**请勿**添加 `nginx.ingress.kubernetes.io/rewrite-target` 注解,这会破坏 Teable 的内部路由。 + + + +## 部署步骤 + + +在部署 Teable 之前,请确保: +1. MinIO bucket(`teable-pub` 和 `teable-pvt`)已创建 +2. 公共桶已开启匿名读取权限 +3. PostgreSQL 数据库 `teable` 已存在 +4. Redis 服务可访问 + + +1. 创建命名空间(如果不存在): +```sh +kubectl create namespace teable +``` + +2. 创建配置和密钥: +```sh +kubectl apply -f teable-config.yaml +kubectl apply -f teable-secrets.yaml +``` + +3. 部署应用: +```sh +kubectl apply -f teable-deployment.yaml +kubectl apply -f teable-service.yaml +kubectl apply -f teable-ingress.yaml # 可选,如果使用 Ingress +``` + +4. 验证部署: +```sh +# 检查 Pod 状态 +kubectl get pods -n teable -l app=teable + +# 等待 Pod 就绪 +kubectl rollout status deployment/teable -n teable + +# 查看应用日志 +kubectl logs -n teable -l app=teable +``` + +### 配置注意事项 + +1. 敏感信息管理 + - 所有密码、密钥类信息应通过 Secrets 管理 + +2. 数据库配置 + - 确保数据库已创建 teable 数据库 + - 数据库用户需要具备适当权限 + - 建议使用连接池管理数据库连接 + +3. MinIO 配置 + - 确保存储桶已创建且权限正确, 一个公共桶,一个私有桶 + - 公共桶需要完全公开读 + - 内外部访问地址配置正确 + +4. Redis 配置 + - 建议启用 Redis 持久化 + - 配置适当的内存限制 + - 考虑使用 Redis 集群提高可用性 + +5. 安全建议 + - 使用强密码和密钥 + - 启用 TLS/SSL 加密 + - 定期更新证书 + - 限制网络访问范围 + +6. 资源配置 + - 根据实际负载调整资源限制 + - 监控资源使用情况 + - 配置适当的健康检查参数 + +### 故障排查 + +#### 常见问题 + +| 错误信息 | 原因 | 解决方案 | +|---------|------|---------| +| `ECONNREFUSED` 连接 MinIO 失败 | MinIO 内网端点配置错误或不可达 | 检查 `BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT` 格式是否正确:`..svc.cluster.local` | +| `Bucket not found` | MinIO bucket 未创建 | 在部署前使用 `mc mb` 命令创建 bucket | +| `Access Denied` 文件上传失败 | MinIO 凭证错误 | 检查 `BACKEND_STORAGE_MINIO_ACCESS_KEY` 和 `SECRET_KEY` | +| 访问页面显示 `Page not found` | Ingress 配置了 `rewrite-target` | 移除 Ingress 中的 `rewrite-target` 注解 | +| Pod 处于 `CrashLoopBackOff` 状态 | 后端启动失败 | 检查日志,确认 MinIO、数据库、Redis 连接是否正常 | + +#### 诊断命令 + +1. 检查 Pod 状态: +```sh +kubectl describe pod -n teable -l app=teable +``` + +2. 查看应用日志: +```sh +kubectl logs -n teable -l app=teable +kubectl logs -n teable -l app=teable --previous # 如果 Pod 崩溃 +``` + +3. 验证配置: +```sh +kubectl describe configmap teable-config -n teable +kubectl describe secret teable-secrets -n teable +``` + +4. 检查网络连接: +```sh +kubectl exec -it -n teable -- curl -v localhost:3000/health +``` + +5. 测试 MinIO 集群内部连接: +```sh +kubectl run test-minio --rm -it --image=curlimages/curl --restart=Never -n teable -- \ + curl -v http://minio.teable.svc.cluster.local/minio/health/live +``` diff --git a/zh/deploy/one-key.mdx b/zh/deploy/one-key.mdx new file mode 100644 index 00000000..16cb191c --- /dev/null +++ b/zh/deploy/one-key.mdx @@ -0,0 +1,42 @@ +--- +title: "一键云部署" +description: "通过云服务商集成的配置模板,你可以一键部署完整的,可伸缩的 Teable 服务" +--- + + +**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 +[选择部署方式](/zh/deploy/choose)开始。 下方模板当前不可用。 + + +| 优点 | 缺点 | +| ----------- | ------ | +| 无需任何配置,一键启动 | 依赖云服务商 | +| 可以弹性伸缩 | | +| 运维简单 | | + +**系统要求**: + +* 应用节点最低 1c/2g +* 数据库最低 1c/2g + +#### Sealos + +有免费额度,对国内支持友好,按量计费 + +[**一键部署**](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dteable) + +### 订阅与激活(付费功能) + +如果你是**自托管部署**并需要付费功能,请在部署完成后进行订阅并激活许可证: + +1. 在你部署好的 Teable 实例中进入管理员后台,复制 **Instance ID(实例 ID)** +2. 打开自托管订阅页面完成订阅:`https://app.teable.ai/public/pricing?host=self-hosted` +3. 订阅成功后会获得 **License Key(许可证密钥)**,回到实例中完成激活 + +详情请见:[订阅和激活许可证](/zh/deploy/activate) + +### 下一步 + +[配置邮件服务](/zh/deploy/email) + +[配置外部数据库连接](/zh/deploy/database-connection) diff --git a/zh/deploy/production-overview.mdx b/zh/deploy/production-overview.mdx index 21928b32..4eff4fb3 100644 --- a/zh/deploy/production-overview.mdx +++ b/zh/deploy/production-overview.mdx @@ -3,6 +3,12 @@ title: "概览" description: "Teable 生产环境部署的架构、配置建议和部署路径选择" --- + +**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 +[选择部署方式](/zh/deploy/choose)开始。 + + + **推荐场景**:50+ 用户的生产环境 diff --git a/zh/deploy/storage.mdx b/zh/deploy/storage.mdx index 69999d37..471e9e3c 100644 --- a/zh/deploy/storage.mdx +++ b/zh/deploy/storage.mdx @@ -3,6 +3,12 @@ title: "对象存储(S3-compatible)" description: "Teable 生产环境存储指引:public/private 双桶、权限与跨域(CORS)" --- + +**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 +[选择部署方式](/zh/deploy/choose)开始。 对象存储现已随部署资产预配置;全功能平台会为你管理三个存储桶(公开、私有、构建产物)。 + + + 生产环境请使用 **S3-compatible 对象存储**(如 S3、MinIO、兼容 S3 的 OSS/OBS/COS 等),并按 **public/private 双桶**进行配置。 From 2bca021b616b2ae7bf76b732436699164f4a1c61 Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 19:32:56 +0800 Subject: [PATCH 04/12] docs(api): drop setup link into the shelved database-connection page Co-Authored-By: Claude Fable 5 --- en/api-doc/sql-query.mdx | 2 +- zh/api-doc/sql-query.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/en/api-doc/sql-query.mdx b/en/api-doc/sql-query.mdx index 42cda7ba..8523c0d1 100644 --- a/en/api-doc/sql-query.mdx +++ b/en/api-doc/sql-query.mdx @@ -4,7 +4,7 @@ noindex: true --- -**Self-hosted only**: Available for all versions with unlimited connections. For setup, see [Database Connection](/en/deploy/database-connection). +**Self-hosted only**: Available for all versions with unlimited connections. Teable allows you to connect to its underlying PostgreSQL database through external tools for read-only queries. This enables integration with BI tools (PowerBI, Metabase, Superset), database clients (DataGrip, Navicat, TablePlus), low-code platforms (Appsmith, Budibase), and application code. diff --git a/zh/api-doc/sql-query.mdx b/zh/api-doc/sql-query.mdx index 6b54e81e..20b1b481 100644 --- a/zh/api-doc/sql-query.mdx +++ b/zh/api-doc/sql-query.mdx @@ -4,7 +4,7 @@ noindex: true --- -**仅适用于私有化部署**:无连接数限制。开启方式请查看[数据库连接](/zh/deploy/database-connection)。 +**仅适用于私有化部署**:无连接数限制。 Teable 允许您通过外部工具连接底层 PostgreSQL 数据库进行只读查询。支持 BI 工具(PowerBI、Metabase、Superset)、数据库客户端(DataGrip、Navicat、TablePlus)、低代码平台(Appsmith、Budibase)以及应用程序代码。 From d7e5c3e22ef31c94c7b69becece2b16c0598497b Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 19:53:21 +0800 Subject: [PATCH 05/12] docs(self-host): delete shelved legacy deploy pages, redirect old URLs Remove the eight deprecated pages outright (production-overview, k8s, aws, azure, gcp, one-key, storage, database-connection) plus their orphaned screenshots. Deployment-path URLs redirect to Choose Your Path; the two configuration pages (storage, database-connection) redirect to Environment Variables, which now documents PUBLIC_DATABASE_PROXY so the external database connection feature keeps its setup reference. Co-Authored-By: Claude Fable 5 --- docs.json | 64 ++ en/deploy/aws.mdx | 389 -------- en/deploy/azure.mdx | 827 ------------------ en/deploy/database-connection.mdx | 29 - en/deploy/env.mdx | 1 + en/deploy/gcp.mdx | 287 ------ en/deploy/k8s.mdx | 433 --------- en/deploy/one-key.mdx | 48 - en/deploy/production-overview.mdx | 186 ---- en/deploy/storage.mdx | 129 --- ...-05-29-database-connection-overview-en.png | Bin 62649 -> 0 bytes ...-05-29-database-connection-overview-zh.png | Bin 46881 -> 0 bytes zh/deploy/aws.mdx | 388 -------- zh/deploy/azure.mdx | 826 ----------------- zh/deploy/database-connection.mdx | 28 - zh/deploy/env.mdx | 1 + zh/deploy/gcp.mdx | 286 ------ zh/deploy/k8s.mdx | 435 --------- zh/deploy/one-key.mdx | 42 - zh/deploy/production-overview.mdx | 183 ---- zh/deploy/storage.mdx | 128 --- 21 files changed, 66 insertions(+), 4644 deletions(-) delete mode 100644 en/deploy/aws.mdx delete mode 100644 en/deploy/azure.mdx delete mode 100644 en/deploy/database-connection.mdx delete mode 100644 en/deploy/gcp.mdx delete mode 100644 en/deploy/k8s.mdx delete mode 100644 en/deploy/one-key.mdx delete mode 100644 en/deploy/production-overview.mdx delete mode 100644 en/deploy/storage.mdx delete mode 100644 images/selfhosted/database-connection/2026-05-29-database-connection-overview-en.png delete mode 100644 images/selfhosted/database-connection/2026-05-29-database-connection-overview-zh.png delete mode 100644 zh/deploy/aws.mdx delete mode 100644 zh/deploy/azure.mdx delete mode 100644 zh/deploy/database-connection.mdx delete mode 100644 zh/deploy/gcp.mdx delete mode 100644 zh/deploy/k8s.mdx delete mode 100644 zh/deploy/one-key.mdx delete mode 100644 zh/deploy/production-overview.mdx delete mode 100644 zh/deploy/storage.mdx diff --git a/docs.json b/docs.json index 37d72148..8dc66c3f 100644 --- a/docs.json +++ b/docs.json @@ -35,6 +35,70 @@ { "source": "/zh/about", "destination": "/zh/basic/ai/overview" + }, + { + "source": "/en/deploy/production-overview", + "destination": "/en/deploy/choose" + }, + { + "source": "/zh/deploy/production-overview", + "destination": "/zh/deploy/choose" + }, + { + "source": "/en/deploy/k8s", + "destination": "/en/deploy/choose" + }, + { + "source": "/zh/deploy/k8s", + "destination": "/zh/deploy/choose" + }, + { + "source": "/en/deploy/aws", + "destination": "/en/deploy/choose" + }, + { + "source": "/zh/deploy/aws", + "destination": "/zh/deploy/choose" + }, + { + "source": "/en/deploy/azure", + "destination": "/en/deploy/choose" + }, + { + "source": "/zh/deploy/azure", + "destination": "/zh/deploy/choose" + }, + { + "source": "/en/deploy/gcp", + "destination": "/en/deploy/choose" + }, + { + "source": "/zh/deploy/gcp", + "destination": "/zh/deploy/choose" + }, + { + "source": "/en/deploy/one-key", + "destination": "/en/deploy/choose" + }, + { + "source": "/zh/deploy/one-key", + "destination": "/zh/deploy/choose" + }, + { + "source": "/en/deploy/storage", + "destination": "/en/deploy/env" + }, + { + "source": "/zh/deploy/storage", + "destination": "/zh/deploy/env" + }, + { + "source": "/en/deploy/database-connection", + "destination": "/en/deploy/env" + }, + { + "source": "/zh/deploy/database-connection", + "destination": "/zh/deploy/env" } ], "navigation": { diff --git a/en/deploy/aws.mdx b/en/deploy/aws.mdx deleted file mode 100644 index 1cd55aae..00000000 --- a/en/deploy/aws.mdx +++ /dev/null @@ -1,389 +0,0 @@ ---- -title: "Deploy on AWS" -description: "Step-by-step guide: Deploy Teable on AWS using ECS, RDS PostgreSQL, ElastiCache, and S3" ---- - - -**Deprecated.** This page is no longer maintained and has been removed from the -docs navigation. For current deployment guidance, start from -[Choose Your Path](/en/deploy/choose). - - - - -**Recommended for:** Production deployments with 50+ users - - -## Architecture overview - -```mermaid -graph TB - ECS["AWS ECS
(Fargate or EC2)
Teable App: 4+ replicas
Sandbox: 2+ replicas (optional)"] - - RDS["Amazon RDS
Aurora PostgreSQL"] - ElastiCache["Amazon ElastiCache
(Valkey/Redis)
Queue & Cache"] - S3["Amazon S3
Public + Private buckets"] - - ECS --> RDS - ECS --> ElastiCache - ECS --> S3 - - style ECS fill:#ff9900,stroke:#cc7a00,color:#fff - style RDS fill:#527fff,stroke:#3d5fd9,color:#fff - style ElastiCache fill:#c925d1,stroke:#a01ba8,color:#fff - style S3 fill:#569a31,stroke:#3f7224,color:#fff -``` - -## Prerequisites - -- AWS CLI installed and configured -- AWS account with permissions for ECS, RDS, ElastiCache, S3, IAM -- Docker image registry access (or ECR for your built images) - ---- - -## Step 1: Create AWS resources - -### 1.1 Create RDS PostgreSQL - -```bash -aws rds create-db-instance \ - --db-instance-identifier teable-db \ - --db-instance-class db.t3.medium \ - --engine postgres \ - --engine-version 16 \ - --master-username teableadmin \ - --master-user-password '' \ - --allocated-storage 100 \ - --vpc-security-group-ids sg-xxxxxx \ - --db-subnet-group-name your-subnet-group \ - --publicly-accessible false -``` - - -Wait until the instance status is `available`. Get the endpoint: -```bash -aws rds describe-db-instances \ - --db-instance-identifier teable-db \ - --query 'DBInstances[0].Endpoint.Address' --output text -``` - - -### 1.2 Create ElastiCache Redis (for queues) - -```bash -aws elasticache create-cache-cluster \ - --cache-cluster-id teable-cache \ - --engine redis \ - --cache-node-type cache.t3.small \ - --num-cache-nodes 1 \ - --engine-version 7.0 \ - --security-group-ids sg-xxxxxx \ - --cache-subnet-group-name your-subnet-group -``` - - -Get the endpoint: -```bash -aws elasticache describe-cache-clusters \ - --cache-cluster-id teable-cache \ - --show-cache-node-info \ - --query 'CacheClusters[0].CacheNodes[0].Endpoint.Address' --output text -``` - - -### 1.3 Create S3 buckets (public + private) - -```bash -# Create public bucket -aws s3api create-bucket \ - --bucket teable-public- \ - --region us-west-2 \ - --create-bucket-configuration LocationConstraint=us-west-2 - -# Create private bucket -aws s3api create-bucket \ - --bucket teable-private- \ - --region us-west-2 \ - --create-bucket-configuration LocationConstraint=us-west-2 -``` - -**Must-do: Configure public bucket** - -1. **Public read policy**: - -```bash -cat > public-bucket-policy.json </*" - } - ] -} -EOF - -aws s3api put-bucket-policy \ - --bucket teable-public- \ - --policy file://public-bucket-policy.json -``` - -2. **Disable "Block Public Access" for public bucket**: - -```bash -aws s3api put-public-access-block \ - --bucket teable-public- \ - --public-access-block-configuration \ - BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false -``` - -3. **CORS configuration** (allow any origin): - -```bash -cat > cors.json < \ - --cors-configuration file://cors.json -``` - - -**Security reminder**: The public bucket must allow public read. Ensure this complies with your organization's security policy. The private bucket should remain non-public. - - -### 1.4 Create IAM user/role for S3 access - -```bash -# Create IAM user -aws iam create-user --user-name teable-s3-user - -# Attach S3 full access policy (or create a custom policy) -aws iam attach-user-policy \ - --user-name teable-s3-user \ - --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess - -# Create access key -aws iam create-access-key --user-name teable-s3-user -``` - - -Save the `AccessKeyId` and `SecretAccessKey` from the output. - - ---- - -## Step 2: Prepare environment variables - -Create a `.env` file (or ECS task definition environment): - -```bash -# Core -PUBLIC_ORIGIN=https://teable.yourcompany.com -SECRET_KEY= - -# Database -PRISMA_DATABASE_URL=postgresql://teableadmin:@:5432/teable - -# Redis (required for queues) -BACKEND_CACHE_PROVIDER=redis -BACKEND_CACHE_REDIS_URI=redis://:6379/0 - -# Performance cache (optional; can point to the same Redis or a separate cluster) -BACKEND_PERFORMANCE_CACHE=redis://:6379/0 - -# Storage (S3 dual buckets) -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY= -BACKEND_STORAGE_S3_SECRET_KEY= -BACKEND_STORAGE_PUBLIC_BUCKET=teable-public- -BACKEND_STORAGE_PRIVATE_BUCKET=teable-private- -STORAGE_PREFIX=https://teable-public-.s3.us-west-2.amazonaws.com - -``` - - -Generate a strong `SECRET_KEY`: -```bash -openssl rand -base64 32 -``` - - ---- - -## Step 3: Deploy to ECS - -### Option A: Using ECS Fargate (recommended for simplicity) - -1. **Create ECS cluster**: - -```bash -aws ecs create-cluster --cluster-name teable-cluster -``` - -2. **Create task definition** (`teable-task.json`): - -```json -{ - "family": "teable", - "networkMode": "awsvpc", - "requiresCompatibilities": ["FARGATE"], - "cpu": "2048", - "memory": "4096", - "containerDefinitions": [ - { - "name": "teable", - "image": "ghcr.io/teableio/teable:latest", - "portMappings": [ - { - "containerPort": 3000, - "protocol": "tcp" - } - ], - "environment": [ - {"name": "PUBLIC_ORIGIN", "value": "https://teable.yourcompany.com"}, - {"name": "SECRET_KEY", "value": "***REDACTED***"}, - {"name": "PRISMA_DATABASE_URL", "value": "postgresql://..."}, - {"name": "BACKEND_CACHE_PROVIDER", "value": "redis"}, - {"name": "BACKEND_CACHE_REDIS_URI", "value": "redis://..."}, - {"name": "BACKEND_STORAGE_PROVIDER", "value": "s3"}, - {"name": "BACKEND_STORAGE_S3_REGION", "value": "us-west-2"}, - {"name": "BACKEND_STORAGE_S3_ENDPOINT", "value": "https://s3.us-west-2.amazonaws.com"}, - {"name": "BACKEND_STORAGE_S3_ACCESS_KEY", "value": "***"}, - {"name": "BACKEND_STORAGE_S3_SECRET_KEY", "value": "***"}, - {"name": "BACKEND_STORAGE_PUBLIC_BUCKET", "value": "teable-public-xxx"}, - {"name": "BACKEND_STORAGE_PRIVATE_BUCKET", "value": "teable-private-xxx"}, - {"name": "STORAGE_PREFIX", "value": "https://teable-public-xxx.s3.us-west-2.amazonaws.com"} - ], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-group": "/ecs/teable", - "awslogs-region": "us-west-2", - "awslogs-stream-prefix": "teable" - } - } - } - ] -} -``` - -3. **Register task definition**: - -```bash -aws ecs register-task-definition --cli-input-json file://teable-task.json -``` - -4. **Create ECS service**: - -```bash -aws ecs create-service \ - --cluster teable-cluster \ - --service-name teable-service \ - --task-definition teable \ - --desired-count 2 \ - --launch-type FARGATE \ - --network-configuration "awsvpcConfiguration={subnets=[subnet-xxx,subnet-yyy],securityGroups=[sg-xxx],assignPublicIp=ENABLED}" \ - --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:...,containerName=teable,containerPort=3000" -``` - - -You need to create an Application Load Balancer (ALB) and target group separately, then reference it in `--load-balancers`. - - ---- - -## Step 4: Verify deployment - -1. **Check ECS service status**: - -```bash -aws ecs describe-services \ - --cluster teable-cluster \ - --services teable-service -``` - -2. **Check logs**: - -```bash -aws logs tail /ecs/teable --follow -``` - -3. **Access health check**: - -```bash -curl https://teable.yourcompany.com/health -``` - -Expected response: -```json -{"status":"ok"} -``` - -4. **Open Teable in browser**: `https://teable.yourcompany.com` - ---- - -## Troubleshooting - -### Database connection errors - -- Verify RDS security group allows traffic from ECS tasks -- Check `PRISMA_DATABASE_URL` format: `postgresql://user:pass@endpoint:5432/dbname` - -### Redis connection errors - -- Verify ElastiCache security group allows traffic from ECS -- Use the **primary endpoint** (not reader endpoint) - -### S3 access errors - -- Verify IAM user has `s3:GetObject`, `s3:PutObject`, `s3:DeleteObject` permissions -- Check bucket names match exactly in `BACKEND_STORAGE_PUBLIC_BUCKET` / `BACKEND_STORAGE_PRIVATE_BUCKET` -- Ensure public bucket has public read policy and CORS configured - -### Container fails to start - -- Check CloudWatch Logs: `/ecs/teable` -- Verify all required environment variables are set -- Ensure `SECRET_KEY` is set (not empty) - ---- - -## Production best practices - - -For detailed production recommendations including resource sizing, high availability, and scaling strategies, see [Self-Hosted Overview](/en/deploy/production-overview). - - -**AWS-specific tips**: -- Use AWS Secrets Manager for sensitive values -- Enable RDS encryption at rest -- Use VPC endpoints for S3/ECR to avoid internet traffic -- Enable CloudWatch Container Insights for ECS - ---- - -## Related documentation - -- [Self-Hosted Overview](/en/deploy/production-overview) — Architecture, sizing, and scaling -- [Environment Variables Reference](/en/deploy/env) -- [Object Storage (S3-compatible)](/en/deploy/storage) diff --git a/en/deploy/azure.mdx b/en/deploy/azure.mdx deleted file mode 100644 index cd04f1f2..00000000 --- a/en/deploy/azure.mdx +++ /dev/null @@ -1,827 +0,0 @@ ---- -title: "Deploy on Azure" -description: "Step-by-step guide: Deploy Teable on Azure using App Service or AKS, with PostgreSQL Flexible Server, Azure Cache for Redis, and S3-compatible storage" ---- - - -**Deprecated.** This page is no longer maintained and has been removed from the -docs navigation. For current deployment guidance, start from -[Choose Your Path](/en/deploy/choose). - - - - -**Recommended for:** Production deployments with 50+ users - - -## Architecture overview - -```mermaid -graph TB - AppService["Azure App Service
(Linux Container)
Teable App
(scale out as needed)"] - - DB["Azure Database for
PostgreSQL Flexible Server"] - Redis["Azure Cache for Redis
(Queue & Cache)"] - S3["S3-compatible Storage
(AWS S3 or MinIO)
Public + Private buckets"] - - AppService --> DB - AppService --> Redis - AppService --> S3 - - style AppService fill:#0078d4,stroke:#005a9e,color:#fff - style DB fill:#0078d4,stroke:#005a9e,color:#fff - style Redis fill:#0078d4,stroke:#005a9e,color:#fff - style S3 fill:#ff9900,stroke:#cc7a00,color:#fff -``` - - -**Important**: Teable requires **S3-compatible storage**. You can use: -- **AWS S3** (cross-cloud setup) -- **MinIO** (self-hosted on Azure VM/AKS) -- Any other S3-compatible service - -Azure Blob Storage is **not** directly supported because it uses a different API. - - -## Prerequisites - -- Azure CLI installed and logged in -- Azure subscription with permissions for App Service, PostgreSQL, Redis -- Access to an **S3-compatible storage** (e.g., AWS S3 account, or MinIO deployment) - ---- - -## Step 1: Create Azure resources - -### 1.1 Create resource group - -```bash -az group create \ - --name teable-rg \ - --location eastus -``` - -### 1.2 Create PostgreSQL Flexible Server - -```bash -az postgres flexible-server create \ - --resource-group teable-rg \ - --name teable-db \ - --location eastus \ - --admin-user teableadmin \ - --admin-password '' \ - --sku-name Standard_B2s \ - --tier Burstable \ - --storage-size 128 \ - --version 16 \ - --public-access 0.0.0.0-255.255.255.255 -``` - - -Wait until provisioning completes. Get the connection string: -```bash -az postgres flexible-server show \ - --resource-group teable-rg \ - --name teable-db \ - --query "fullyQualifiedDomainName" --output tsv -``` -Result: `teable-db.postgres.database.azure.com` - - -### 1.3 Create Azure Cache for Redis - -```bash -az redis create \ - --resource-group teable-rg \ - --name teable-cache \ - --location eastus \ - --sku Basic \ - --vm-size c0 \ - --minimum-tls-version 1.2 -``` - - -Get the access key: -```bash -az redis list-keys \ - --resource-group teable-rg \ - --name teable-cache \ - --query "primaryKey" --output tsv -``` - - ---- - -## Step 2: Set up S3-compatible storage - -Since Azure Blob Storage is not S3-compatible, choose one of the following options: - -### Option A: Use AWS S3 (cross-cloud) - -This is the simplest approach if you already have AWS access or don't want to manage MinIO. - -1. **Create S3 buckets on AWS** (see [AWS deployment guide](/en/deploy/aws) steps 1.3-1.4) - - Public bucket: `teable-public-` - - Private bucket: `teable-private-` - -2. **Configure public bucket** (see [Object Storage guide](/en/deploy/storage)): - - Enable public read access - - Configure CORS to allow any origin - -You'll use these environment variables: -```bash -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY= -BACKEND_STORAGE_S3_SECRET_KEY= -BACKEND_STORAGE_PUBLIC_BUCKET=teable-public- -BACKEND_STORAGE_PRIVATE_BUCKET=teable-private- -STORAGE_PREFIX=https://teable-public-.s3.us-west-2.amazonaws.com -``` - -### Option B: Deploy MinIO on Azure - -If you prefer to keep everything in Azure, deploy MinIO as an S3-compatible gateway. - -**Recommended image**: `minio/minio:RELEASE.2025-04-22T22-12-26Z` - -**Quick setup (Azure VM)**: - -1. Create a VM and install MinIO: -```bash -wget https://dl.min.io/server/minio/release/linux-amd64/minio -chmod +x minio -sudo mv minio /usr/local/bin/ - -# Start MinIO -export MINIO_ROOT_USER=admin -export MINIO_ROOT_PASSWORD= -minio server /data --console-address ":9001" -``` - -2. Create buckets via MinIO console (port 9001) or CLI: -```bash -mc alias set myminio http://your-vm-ip:9000 admin -mc mb myminio/public -mc mb myminio/private -mc policy set download myminio/public -``` - -You'll use these environment variables: -```bash -BACKEND_STORAGE_PROVIDER=minio -BACKEND_STORAGE_MINIO_ENDPOINT= -BACKEND_STORAGE_MINIO_PORT=443 -BACKEND_STORAGE_MINIO_USE_SSL=true -BACKEND_STORAGE_MINIO_ACCESS_KEY=admin -BACKEND_STORAGE_MINIO_SECRET_KEY= -BACKEND_STORAGE_PUBLIC_BUCKET=public -BACKEND_STORAGE_PRIVATE_BUCKET=private -STORAGE_PREFIX=https:// -``` - ---- - -## Step 3: Prepare environment variables - -Create a file `app-settings.txt` with all required variables: - -```bash -# Core -PUBLIC_ORIGIN=https://teable-app.azurewebsites.net -SECRET_KEY= - -# Database -PRISMA_DATABASE_URL=postgresql://teableadmin:@teable-db.postgres.database.azure.com:5432/postgres?sslmode=require - -# Redis (required for queues) -BACKEND_CACHE_PROVIDER=redis -BACKEND_CACHE_REDIS_URI=rediss://:@teable-cache.redis.cache.windows.net:6380/0 - -# Performance cache (optional; can point to the same Redis) -BACKEND_PERFORMANCE_CACHE=rediss://:@teable-cache.redis.cache.windows.net:6380/0 - -# Storage (S3-compatible) -# For Option A (AWS S3): -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY= -BACKEND_STORAGE_S3_SECRET_KEY= -BACKEND_STORAGE_PUBLIC_BUCKET=teable-public- -BACKEND_STORAGE_PRIVATE_BUCKET=teable-private- -STORAGE_PREFIX=https://teable-public-.s3.us-west-2.amazonaws.com - -# For Option B (MinIO): -# BACKEND_STORAGE_PROVIDER=minio -# BACKEND_STORAGE_MINIO_ENDPOINT= -# BACKEND_STORAGE_MINIO_PORT=443 -# BACKEND_STORAGE_MINIO_USE_SSL=true -# BACKEND_STORAGE_MINIO_ACCESS_KEY=admin -# BACKEND_STORAGE_MINIO_SECRET_KEY= -# BACKEND_STORAGE_PUBLIC_BUCKET=public -# BACKEND_STORAGE_PRIVATE_BUCKET=private -# STORAGE_PREFIX=https:// -``` - - -Generate a strong `SECRET_KEY`: -```bash -openssl rand -base64 32 -``` - - ---- - -## Step 4: Deploy to Azure App Service - -### 4.1 Create App Service Plan - -```bash -az appservice plan create \ - --resource-group teable-rg \ - --name teable-plan \ - --location eastus \ - --is-linux \ - --sku P1v3 -``` - -### 4.2 Create Web App (container-based) - -```bash -az webapp create \ - --resource-group teable-rg \ - --plan teable-plan \ - --name teable-app \ - --deployment-container-image-name ghcr.io/teableio/teable:latest -``` - -### 4.3 Configure container settings - -```bash -az webapp config container set \ - --resource-group teable-rg \ - --name teable-app \ - --docker-custom-image-name ghcr.io/teableio/teable:latest \ - --docker-registry-server-url https://ghcr.io -``` - -### 4.4 Set environment variables - -```bash -# Convert app-settings.txt to JSON format and apply -az webapp config appsettings set \ - --resource-group teable-rg \ - --name teable-app \ - --settings \ - WEBSITES_PORT=3000 \ - PUBLIC_ORIGIN="https://teable-app.azurewebsites.net" \ - SECRET_KEY="" \ - PRISMA_DATABASE_URL="postgresql://..." \ - BACKEND_CACHE_PROVIDER="redis" \ - BACKEND_CACHE_REDIS_URI="rediss://..." \ - BACKEND_PERFORMANCE_CACHE="rediss://..." \ - BACKEND_STORAGE_PROVIDER="s3" \ - BACKEND_STORAGE_S3_REGION="us-west-2" \ - BACKEND_STORAGE_S3_ENDPOINT="https://s3.us-west-2.amazonaws.com" \ - BACKEND_STORAGE_S3_ACCESS_KEY="***" \ - BACKEND_STORAGE_S3_SECRET_KEY="***" \ - BACKEND_STORAGE_PUBLIC_BUCKET="teable-public-xxx" \ - BACKEND_STORAGE_PRIVATE_BUCKET="teable-private-xxx" \ - STORAGE_PREFIX="https://teable-public-xxx.s3.us-west-2.amazonaws.com" -``` - -### 4.5 Configure health check - -```bash -az webapp config set \ - --resource-group teable-rg \ - --name teable-app \ - --generic-configurations '{"healthCheckPath": "/health"}' -``` - -### 4.6 Restart the app - -```bash -az webapp restart \ - --resource-group teable-rg \ - --name teable-app -``` - ---- - -## Step 5: Verify deployment - -1. **Check app status**: - -```bash -az webapp show \ - --resource-group teable-rg \ - --name teable-app \ - --query "state" --output tsv -``` - -Expected: `Running` - -2. **View logs**: - -```bash -az webapp log tail \ - --resource-group teable-rg \ - --name teable-app -``` - -3. **Test health check**: - -```bash -curl https://teable-app.azurewebsites.net/health -``` - -Expected response: -```json -{"status":"ok"} -``` - -4. **Open Teable in browser**: `https://teable-app.azurewebsites.net` - ---- - -## Troubleshooting - -### Database connection errors - -- Verify PostgreSQL firewall allows Azure services -- Check connection string includes `?sslmode=require` -- Ensure database name is correct (default is `postgres`, not `teable`) - -### Redis connection errors - -- Use `rediss://` (with double 's') for TLS connections -- Use port **6380** (not 6379) for Azure Cache for Redis -- Verify access key is correct - -### S3 access errors (Option A: AWS S3) - -- Verify AWS credentials are correct -- Check bucket names match exactly -- Ensure public bucket has public read policy and CORS configured -- Test S3 access from Azure: `curl https://s3.us-west-2.amazonaws.com` should work - -### Container fails to start - -- Check logs: `az webapp log tail ...` -- Verify all required environment variables are set -- Ensure `WEBSITES_PORT=3000` is set - ---- - -## Production best practices - - -For detailed production recommendations including resource sizing, high availability, and scaling strategies, see [Self-Hosted Overview](/en/deploy/production-overview). - - -**Azure-specific tips**: -- Use Azure Key Vault for sensitive values -- Enable HTTPS only: `az webapp update --https-only true` -- Use VNet Integration to restrict database/redis access -- Enable Application Insights for monitoring - -**Custom domain setup**: -```bash -# Add custom domain -az webapp config hostname add \ - --resource-group teable-rg \ - --webapp-name teable-app \ - --hostname teable.yourcompany.com - -# Create managed SSL certificate -az webapp config ssl create \ - --resource-group teable-rg \ - --name teable-app \ - --hostname teable.yourcompany.com -``` - ---- - -## Alternative: Deploy on AKS (Azure Kubernetes Service) - -For larger deployments or teams already using Kubernetes, AKS provides better scalability and control. - -### AKS Architecture - -```mermaid -graph TB - subgraph AKS["Azure Kubernetes Service"] - Ingress["Ingress Controller
(AGIC or NGINX)"] - Pods["Teable Pods
(scale horizontally)"] - end - - DB["Azure Database for
PostgreSQL Flexible Server"] - Redis["Azure Cache for Redis"] - S3["S3-compatible Storage
(MinIO or AWS S3)"] - - Ingress --> Pods - Pods --> DB - Pods --> Redis - Pods --> S3 - - style AKS fill:#326ce5,stroke:#1a4ba0,color:#fff - style DB fill:#0078d4,stroke:#005a9e,color:#fff - style Redis fill:#0078d4,stroke:#005a9e,color:#fff - style S3 fill:#ff9900,stroke:#cc7a00,color:#fff -``` - -### Step 1: Create AKS Cluster - -```bash -# Create AKS cluster -az aks create \ - --resource-group teable-rg \ - --name teable-aks \ - --location eastus \ - --node-count 2 \ - --node-vm-size Standard_D4s_v3 \ - --enable-managed-identity \ - --generate-ssh-keys - -# Get credentials -az aks get-credentials \ - --resource-group teable-rg \ - --name teable-aks -``` - -### Step 2: Create Azure Managed Services - -Create PostgreSQL and Redis using the same commands from [Step 1.2](#12-create-postgresql-flexible-server) and [Step 1.3](#13-create-azure-cache-for-redis). - - -For AKS, you may want to enable VNet integration to secure the connection: - -```bash -# Get AKS VNet ID -AKS_VNET=$(az aks show \ - --resource-group teable-rg \ - --name teable-aks \ - --query "networkProfile.networkPlugin" -o tsv) - -# Configure PostgreSQL to accept connections from AKS VNet -az postgres flexible-server vnet-rule create \ - --resource-group teable-rg \ - --server-name teable-db \ - --name aks-access \ - --vnet-name \ - --subnet -``` - - -### Step 3: Deploy MinIO to AKS (Optional) - -If you prefer to keep storage within Azure, deploy MinIO to AKS: - -```yaml -# minio-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: minio -spec: - replicas: 1 - selector: - matchLabels: - app: minio - template: - metadata: - labels: - app: minio - spec: - containers: - - name: minio - image: minio/minio:latest - args: - - server - - /data - - --console-address - - ":9001" - env: - - name: MINIO_ROOT_USER - value: "minioadmin" - - name: MINIO_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: minio-secrets - key: password - ports: - - containerPort: 9000 - - containerPort: 9001 - volumeMounts: - - name: data - mountPath: /data - volumes: - - name: data - persistentVolumeClaim: - claimName: minio-pvc ---- -apiVersion: v1 -kind: Service -metadata: - name: minio -spec: - ports: - - name: api - port: 9000 - targetPort: 9000 - - name: console - port: 9001 - targetPort: 9001 - selector: - app: minio ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: minio-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 50Gi - storageClassName: managed-premium -``` - - -For production MinIO deployments, consider using the [MinIO Operator](https://min.io/docs/minio/kubernetes/upstream/) for distributed mode with data redundancy. - - -### Step 4: Create Kubernetes Configurations - -Create the ConfigMap with Azure-specific settings: - -```yaml -# teable-config.yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: teable-config -data: - PUBLIC_ORIGIN: "https://teable.yourcompany.com" - - # Storage (MinIO in AKS) - BACKEND_STORAGE_PROVIDER: "minio" - BACKEND_STORAGE_MINIO_ENDPOINT: "minio.yourcompany.com" - STORAGE_PREFIX: "https://minio.yourcompany.com" - BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT: "minio.default.svc.cluster.local" - BACKEND_STORAGE_MINIO_PORT: "443" - BACKEND_STORAGE_MINIO_INTERNAL_PORT: "9000" - BACKEND_STORAGE_MINIO_USE_SSL: "true" - - # Cache - BACKEND_CACHE_PROVIDER: "redis" - - # Other - NEXT_ENV_IMAGES_ALL_REMOTE: "true" - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" -``` - -Create secrets with Azure service credentials: - -```yaml -# teable-secrets.yaml -apiVersion: v1 -kind: Secret -metadata: - name: teable-secrets -type: Opaque -stringData: - # PostgreSQL (Azure Flexible Server) - PRISMA_DATABASE_URL: "postgresql://teableadmin:@teable-db.postgres.database.azure.com:5432/postgres?sslmode=require" - - # Application secrets - SECRET_KEY: "" - - # MinIO credentials - BACKEND_STORAGE_PUBLIC_BUCKET: "public" - BACKEND_STORAGE_PRIVATE_BUCKET: "private" - BACKEND_STORAGE_MINIO_ACCESS_KEY: "minioadmin" - BACKEND_STORAGE_MINIO_SECRET_KEY: "" - - # Redis (Azure Cache for Redis) - BACKEND_CACHE_REDIS_URI: "rediss://:@teable-cache.redis.cache.windows.net:6380/0" -``` - -### Step 5: Deploy Teable - -```yaml -# teable-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: teable -spec: - replicas: 2 # Scale as needed - selector: - matchLabels: - app: teable - template: - metadata: - labels: - app: teable - spec: - initContainers: - - name: db-migrate - image: ghcr.io/teableio/teable:latest - args: - - migrate-only - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - limits: - cpu: 1000m - memory: 1024Mi - containers: - - name: teable - image: ghcr.io/teableio/teable:latest - args: - - skip-migrate - ports: - - containerPort: 3000 - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - requests: - cpu: 500m - memory: 1Gi - limits: - cpu: 2000m - memory: 4Gi - livenessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 30 - periodSeconds: 30 - readinessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 15 - periodSeconds: 10 ---- -apiVersion: v1 -kind: Service -metadata: - name: teable -spec: - ports: - - port: 3000 - targetPort: 3000 - selector: - app: teable -``` - -### Step 6: Configure Ingress - -**Option A: Using NGINX Ingress Controller** - -```bash -# Install NGINX Ingress -helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx -helm install ingress-nginx ingress-nginx/ingress-nginx \ - --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz -``` - -```yaml -# teable-ingress.yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: teable - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/proxy-body-size: "100m" - cert-manager.io/cluster-issuer: letsencrypt-prod -spec: - tls: - - hosts: - - teable.yourcompany.com - secretName: teable-tls - rules: - - host: teable.yourcompany.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: teable - port: - number: 3000 -``` - -**Option B: Using Azure Application Gateway Ingress Controller (AGIC)** - -```bash -# Enable AGIC addon -az aks enable-addons \ - --resource-group teable-rg \ - --name teable-aks \ - --addons ingress-appgw \ - --appgw-name teable-appgw \ - --appgw-subnet-cidr "10.225.0.0/16" -``` - -```yaml -# teable-ingress-agic.yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: teable - annotations: - kubernetes.io/ingress.class: azure/application-gateway - appgw.ingress.kubernetes.io/backend-path-prefix: "/" - appgw.ingress.kubernetes.io/ssl-redirect: "true" -spec: - tls: - - hosts: - - teable.yourcompany.com - secretName: teable-tls - rules: - - host: teable.yourcompany.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: teable - port: - number: 3000 -``` - -### Step 7: Apply All Configurations - -```bash -# Apply configurations -kubectl apply -f teable-config.yaml -kubectl apply -f teable-secrets.yaml -kubectl apply -f minio-deployment.yaml # If using MinIO in AKS -kubectl apply -f teable-deployment.yaml -kubectl apply -f teable-ingress.yaml - -# Verify deployment -kubectl get pods -l app=teable -kubectl logs -l app=teable -``` - -### AKS Production Recommendations - -1. **Autoscaling**: -```bash -# Enable Horizontal Pod Autoscaler -kubectl autoscale deployment teable --cpu-percent=70 --min=2 --max=10 - -# Enable Cluster Autoscaler -az aks update \ - --resource-group teable-rg \ - --name teable-aks \ - --enable-cluster-autoscaler \ - --min-count 2 \ - --max-count 5 -``` - -2. **Use Azure Key Vault** for secrets instead of Kubernetes secrets: -```bash -# Enable Key Vault secrets provider -az aks enable-addons \ - --resource-group teable-rg \ - --name teable-aks \ - --addons azure-keyvault-secrets-provider -``` - -3. **Enable Azure Monitor**: -```bash -az aks enable-addons \ - --resource-group teable-rg \ - --name teable-aks \ - --addons monitoring -``` - -4. **Use Azure Private Link** for PostgreSQL and Redis to keep traffic within Azure network. - ---- - -## Related documentation - -- [Self-Hosted Overview](/en/deploy/production-overview) — Architecture, sizing, and scaling -- [Environment Variables Reference](/en/deploy/env) -- [Object Storage (S3-compatible)](/en/deploy/storage) -- [Full-featured platform](/en/deploy/agent-runtime) (AI features and App Builder) -- [AWS Deployment](/en/deploy/aws) (for S3 setup if using cross-cloud storage) diff --git a/en/deploy/database-connection.mdx b/en/deploy/database-connection.mdx deleted file mode 100644 index 2c7e1f24..00000000 --- a/en/deploy/database-connection.mdx +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: "Enable External Database Connection" -description: "Automatically create database roles in the application to enable secure access to base data from external applications" ---- - - -**Deprecated.** This page is no longer maintained and has been removed from the -docs navigation. For current deployment guidance, start from -[Choose Your Path](/en/deploy/choose). - - - -External database connection overview - -To enable this feature, you need to configure the additional environment variable `PUBLIC_DATABASE_PROXY` - -Configure this parameter with an externally accessible database IP or domain name + port number. If you're using the docker-compose deployment method mentioned earlier, the external access port is 42345. For other deployment methods, please configure according to your specific setup. - -``` -PUBLIC_DATABASE_PROXY=db-proxy.example.com:port -``` - -## More Environment Variables - -[Environment Variables](/en/deploy/env) diff --git a/en/deploy/env.mdx b/en/deploy/env.mdx index fec2ba84..b23d4384 100644 --- a/en/deploy/env.mdx +++ b/en/deploy/env.mdx @@ -131,6 +131,7 @@ see [Agent Runtime Architecture](/en/deploy/agent-runtime). | TELEMETRY_REPORT_DISABLED | Disable self-hosted telemetry reporting, including license compliance reports from connected instances | false | - | true | | **Database Configuration** | | | | | | PRISMA_DATABASE_URL | Database connection URL, must be configured | - | Yes | postgresql://teable:teable@127.0.0.1:5432/teable | +| PUBLIC_DATABASE_PROXY | Externally reachable database address (host:port) used in the credentials generated by the base "Database Connection" panel; set it to enable external read-only SQL access | - | - | db.example.com:42345 | | PRISMA_TRANSACTION_TIMEOUT | Maximum time (ms) a transaction can run before timing out. Increase for long-running transactions (e.g., bulk updates with many foreign keys) | 5000 | - | 60000 | | PRISMA_TRANSACTION_MAX_WAIT | Maximum time (ms) to wait to acquire a transaction from the pool | 2000 | - | 5000 | | BIG_TRANSACTION_TIMEOUT | Timeout (ms) for large internal transactions, such as exporting large bases | 600000 | - | 1200000 | diff --git a/en/deploy/gcp.mdx b/en/deploy/gcp.mdx deleted file mode 100644 index 6e4d34ee..00000000 --- a/en/deploy/gcp.mdx +++ /dev/null @@ -1,287 +0,0 @@ ---- -title: "Deploy on GCP" -description: "Step-by-step guide: Deploy Teable on Google Cloud Platform using Cloud Run/GKE, Cloud SQL, Memorystore, and S3-compatible storage" ---- - - -**Deprecated.** This page is no longer maintained and has been removed from the -docs navigation. For current deployment guidance, start from -[Choose Your Path](/en/deploy/choose). - - - - -**Recommended for:** Production deployments with 50+ users - - -## Architecture overview - -```mermaid -graph TB - CloudRun["Cloud Run / GKE
Teable App
(auto-scaling)"] - - CloudSQL["Cloud SQL
PostgreSQL"] - Memorystore["Memorystore for Redis
(Queue & Cache)"] - S3["S3-compatible Storage
(AWS S3 or MinIO)
Public + Private buckets"] - - CloudRun --> CloudSQL - CloudRun --> Memorystore - CloudRun --> S3 - - style CloudRun fill:#4285f4,stroke:#2b5fab,color:#fff - style CloudSQL fill:#4285f4,stroke:#2b5fab,color:#fff - style Memorystore fill:#ea4335,stroke:#b8321e,color:#fff - style S3 fill:#ff9900,stroke:#cc7a00,color:#fff -``` - - -**Important**: Teable requires **S3-compatible storage**. You can use: -- **AWS S3** (cross-cloud setup) -- **MinIO** (self-hosted on GCE/GKE) -- Any other S3-compatible service - -Google Cloud Storage (GCS) is **not** directly supported because it uses a different API. - - -## Prerequisites - -- `gcloud` CLI installed and authenticated -- GCP project with billing enabled -- Access to **S3-compatible storage** (e.g., AWS S3 account or MinIO deployment) - ---- - -## Step 1: Create GCP resources - -### 1.1 Set project and region - -```bash -export PROJECT_ID=your-project-id -export REGION=us-central1 - -gcloud config set project $PROJECT_ID -``` - -### 1.2 Create Cloud SQL (PostgreSQL) - -```bash -gcloud sql instances create teable-db \ - --database-version=POSTGRES_16 \ - --tier=db-custom-2-7680 \ - --region=$REGION \ - --root-password= \ - --storage-type=SSD \ - --storage-size=100GB -``` - - -Get connection name: -```bash -gcloud sql instances describe teable-db \ - --format='value(connectionName)' -``` -Result: `your-project:region:teable-db` - - -Create database: -```bash -gcloud sql databases create teable --instance=teable-db -``` - -### 1.3 Create Memorystore (Redis) - -```bash -gcloud redis instances create teable-cache \ - --size=1 \ - --region=$REGION \ - --redis-version=redis_7_0 -``` - - -Get Redis host: -```bash -gcloud redis instances describe teable-cache \ - --region=$REGION \ - --format='value(host)' -``` - - ---- - -## Step 2: Set up S3-compatible storage - -Since Google Cloud Storage is not S3-compatible, choose one of the following options: - -### Option A: Use AWS S3 (cross-cloud) - -1. **Create S3 buckets on AWS** (see [AWS deployment guide](/en/deploy/aws) steps 1.3-1.4) -2. **Configure public bucket** (see [Object Storage guide](/en/deploy/storage)) - -Environment variables: -```bash -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY= -BACKEND_STORAGE_S3_SECRET_KEY= -BACKEND_STORAGE_PUBLIC_BUCKET=teable-public- -BACKEND_STORAGE_PRIVATE_BUCKET=teable-private- -STORAGE_PREFIX=https://teable-public-.s3.us-west-2.amazonaws.com -``` - -### Option B: Deploy MinIO on GCP - -Deploy MinIO on a GCE instance or GKE cluster to provide S3-compatible storage. - -**Recommended image**: `minio/minio:RELEASE.2025-04-22T22-12-26Z` - -**Quick setup (GCE VM)**: - -```bash -# Create VM -gcloud compute instances create minio-server \ - --machine-type=e2-medium \ - --zone=us-central1-a \ - --image-family=ubuntu-2204-lts \ - --image-project=ubuntu-os-cloud \ - --boot-disk-size=100GB - -# SSH and install MinIO -gcloud compute ssh minio-server --zone=us-central1-a - -# On the VM: -wget https://dl.min.io/server/minio/release/linux-amd64/minio -chmod +x minio -sudo mv minio /usr/local/bin/ - -export MINIO_ROOT_USER=admin -export MINIO_ROOT_PASSWORD= -minio server /data --console-address ":9001" -``` - -Create buckets and configure access (see Azure guide Option B for details). - ---- - -## Step 3: Prepare environment variables - -Create `.env` file: - -```bash -# Core -PUBLIC_ORIGIN=https://teable.yourcompany.com -SECRET_KEY= - -# Database (Cloud SQL) -PRISMA_DATABASE_URL=postgresql://postgres:@/teable?host=/cloudsql/ - -# Redis (Memorystore) -BACKEND_CACHE_PROVIDER=redis -BACKEND_CACHE_REDIS_URI=redis://:6379/0 -BACKEND_PERFORMANCE_CACHE=redis://:6379/0 - -# Storage (S3-compatible) -# For Option A (AWS S3): -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY= -BACKEND_STORAGE_S3_SECRET_KEY= -BACKEND_STORAGE_PUBLIC_BUCKET=teable-public- -BACKEND_STORAGE_PRIVATE_BUCKET=teable-private- -STORAGE_PREFIX=https://teable-public-.s3.us-west-2.amazonaws.com -``` - ---- - -## Step 4: Deploy to Cloud Run - -### 4.1 Build and push container (or use pre-built image) - -```bash -# Option 1: Use pre-built image -export IMAGE=ghcr.io/teableio/teable:latest - -# Option 2: Build and push to GCR -# gcloud builds submit --tag gcr.io/$PROJECT_ID/teable -# export IMAGE=gcr.io/$PROJECT_ID/teable -``` - -### 4.2 Deploy to Cloud Run - -```bash -gcloud run deploy teable \ - --image=$IMAGE \ - --platform=managed \ - --region=$REGION \ - --allow-unauthenticated \ - --port=3000 \ - --set-env-vars="PUBLIC_ORIGIN=https://teable.yourcompany.com" \ - --set-env-vars="SECRET_KEY=" \ - --set-env-vars="PRISMA_DATABASE_URL=postgresql://..." \ - --set-env-vars="BACKEND_CACHE_PROVIDER=redis" \ - --set-env-vars="BACKEND_CACHE_REDIS_URI=redis://..." \ - --set-env-vars="BACKEND_STORAGE_PROVIDER=s3" \ - --set-env-vars="BACKEND_STORAGE_S3_REGION=us-west-2" \ - --set-env-vars="BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com" \ - --set-env-vars="BACKEND_STORAGE_S3_ACCESS_KEY=***" \ - --set-env-vars="BACKEND_STORAGE_S3_SECRET_KEY=***" \ - --set-env-vars="BACKEND_STORAGE_PUBLIC_BUCKET=teable-public-xxx" \ - --set-env-vars="BACKEND_STORAGE_PRIVATE_BUCKET=teable-private-xxx" \ - --set-env-vars="STORAGE_PREFIX=https://teable-public-xxx.s3.us-west-2.amazonaws.com" \ - --add-cloudsql-instances= \ - --vpc-connector= -``` - - -For Cloud SQL Unix socket access, use `--add-cloudsql-instances`. -For Memorystore access, create a VPC connector first. - - ---- - -## Step 5: Verify deployment - -1. **Get service URL**: - -```bash -gcloud run services describe teable \ - --region=$REGION \ - --format='value(status.url)' -``` - -2. **Test health check**: - -```bash -curl https:///health -``` - -Expected: `{"status":"ok"}` - -3. **View logs**: - -```bash -gcloud run services logs read teable --region=$REGION -``` - ---- - -## Production best practices - - -For detailed production recommendations including resource sizing, high availability, and scaling strategies, see [Self-Hosted Overview](/en/deploy/production-overview). - - -**GCP-specific tips**: -- Enable Cloud Run minimum instances (2+) for high availability -- Use Secret Manager for sensitive values -- Enable Cloud Monitoring and set up alerts -- Map a custom domain to Cloud Run service - ---- - -## Related documentation - -- [Self-Hosted Overview](/en/deploy/production-overview) — Architecture, sizing, and scaling -- [Environment Variables Reference](/en/deploy/env) -- [Object Storage (S3-compatible)](/en/deploy/storage) diff --git a/en/deploy/k8s.mdx b/en/deploy/k8s.mdx deleted file mode 100644 index cbc5912e..00000000 --- a/en/deploy/k8s.mdx +++ /dev/null @@ -1,433 +0,0 @@ ---- -title: "Kubernetes Deployment" ---- - - -**Deprecated.** This page is no longer maintained and has been removed from the -docs navigation. For current deployment guidance, start from -[Choose Your Path](/en/deploy/choose). Kubernetes deployments now use the full-featured Helm chart — see [Agent Runtime Architecture](/en/deploy/agent-runtime). - - - - -**Recommended for:** Production deployments with 50+ users - - - -For resource sizing recommendations (CPU, memory, replicas), see [Self-Hosted Overview → Resource Recommendations](/en/deploy/production-overview#resource-recommendations). - - -## Prerequisites -### Teable Images -- Application image: `ghcr.io/teableio/teable:latest` - -### Runtime Environment -- Running Kubernetes cluster - -### Required External Services -#### Database Service -- PostgreSQL database: postgres:15.4 (version > 12) - - PG image: `postgres:15.4` - -#### Cache Service -- Redis cache service: redis:7.2.4 (version > 5) - - Redis image: `redis:7.2.4` - -#### Object Storage Service -- MinIO object storage service: `minio/minio:RELEASE.2025-04-22T22-12-26Z` - -### Required Tools -- kubectl command-line tool - -## Dependency Component Configuration -### File Storage (MinIO) -File storage service must be accessible from the public internet (directly accessible by end users) - -Two buckets need to be created in advance for file storage: - -| Bucket | Environment Variable | Permission | -|--------|---------------------|------------| -| Public bucket | `BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub` | **Public read (anonymous access required)** | -| Private bucket | `BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt` | Private (default) | - - -The public bucket **must** have anonymous read access enabled. Without this, users cannot view shared images, avatars, and other public files. - - -#### Create Buckets Using MinIO Client (mc) - -Before deploying Teable, create the required buckets: - -```sh -# Set up MinIO client alias -mc alias set teable-minio https://minio.example.com YOUR_ACCESS_KEY YOUR_SECRET_KEY - -# Create public bucket and set anonymous read access -mc mb --ignore-existing teable-minio/teable-pub -mc anonymous set public teable-minio/teable-pub - -# Create private bucket -mc mb --ignore-existing teable-minio/teable-pvt - -# Verify buckets exist -mc ls teable-minio -# Expected output: -# [2024-01-01 00:00:00 UTC] 0B teable-pub/ -# [2024-01-01 00:00:00 UTC] 0B teable-pvt/ -``` - -#### Teable MinIO Environment Variables Overview - -```sh -# Fixed value -BACKEND_STORAGE_PROVIDER=minio -# Public bucket -BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub -# Private bucket -BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt -# Public endpoint, important! Must be accessible by end users -BACKEND_STORAGE_MINIO_ENDPOINT=minio.example.com -# Same as above but with protocol -STORAGE_PREFIX=https://minio.example.com -# Internal network endpoint (Kubernetes DNS format: ..svc.cluster.local) -# ⚠️ Replace with your actual namespace, e.g., minio.teable.svc.cluster.local -BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT=minio.teable.svc.cluster.local -# Public port, typically 443 or 9000 -BACKEND_STORAGE_MINIO_PORT=443 -# Internal network port, typically 80 or 9000 -BACKEND_STORAGE_MINIO_INTERNAL_PORT=80 -# Enable HTTPS, note: if Teable uses HTTPS, MinIO must also use HTTPS to avoid CORS issues -BACKEND_STORAGE_MINIO_USE_SSL="true" -# Admin account -BACKEND_STORAGE_MINIO_ACCESS_KEY=root -# Admin password -BACKEND_STORAGE_MINIO_SECRET_KEY=rootPassword -``` - -### Database -Create a database account with administrative privileges, a database, and set a password. -Environment variables example: - - Database name: teable - - Password: your-password - - Username: postgres - - Port: 5432 -```sh -PRISMA_DATABASE_URL="postgresql://postgres:your-password@your-postgres-host:5432/teable" -``` - -### Redis Cache -Teable only needs the internal network address for Redis cache configuration. (Note: Redis manages both cache and queues, it's essential. Data should be backed up regularly) -Environment variables example: -```sh -BACKEND_CACHE_REDIS_URI="redis://username:password@your-redis-host:6379/0" -``` - -## Create Configuration Files - -```yaml teable-config.yaml (Non-sensitive configuration) -apiVersion: v1 -kind: ConfigMap -metadata: - name: teable-config - namespace: teable # Replace with your namespace -data: - # Application base configuration, public access domain - PUBLIC_ORIGIN: "https://your-domain.com" - - # Storage configuration - BACKEND_STORAGE_PROVIDER: "minio" - # Bucket names (must be created in MinIO before deployment) - BACKEND_STORAGE_PUBLIC_BUCKET: "teable-pub" - BACKEND_STORAGE_PRIVATE_BUCKET: "teable-pvt" - # Public endpoint, important! Must be accessible by end users - BACKEND_STORAGE_MINIO_ENDPOINT: "minio.example.com" - # Same as above but with protocol - STORAGE_PREFIX: "https://minio.example.com" - # Internal endpoint (Kubernetes DNS format: ..svc.cluster.local) - # ⚠️ Replace 'teable' with your actual namespace - BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT: "minio.teable.svc.cluster.local" - # Public port, typically 443 or 9000 - BACKEND_STORAGE_MINIO_PORT: "443" - # Internal port, typically 80 or 9000 - BACKEND_STORAGE_MINIO_INTERNAL_PORT: "80" - # Enable HTTPS, note: if Teable uses HTTPS, MinIO must also use HTTPS to avoid CORS issues - BACKEND_STORAGE_MINIO_USE_SSL: "true" - - # Cache configuration, fixed value - BACKEND_CACHE_PROVIDER: "redis" - - # Other configurations, fixed values - NEXT_ENV_IMAGES_ALL_REMOTE: "true" - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - # Keep this when using self-signed certificates - NODE_TLS_REJECT_UNAUTHORIZED: '0' -``` - -```yaml teable-secrets.yaml (Sensitive information) -apiVersion: v1 -kind: Secret -metadata: - name: teable-secrets - namespace: teable # Replace with your namespace -type: Opaque -stringData: - # Database sensitive information - PRISMA_DATABASE_URL: "postgresql://postgres:your-password@your-postgres-host:5432/teable" - - # Application secrets - BACKEND_JWT_SECRET: "your-jwt-secret" - BACKEND_SESSION_SECRET: "your-session-secret" - - # MinIO authentication - BACKEND_STORAGE_MINIO_ACCESS_KEY: "your-minio-access-key" - BACKEND_STORAGE_MINIO_SECRET_KEY: "your-minio-secret-key" - - # Redis authentication - BACKEND_CACHE_REDIS_URI: "redis://username:password@your-redis-host:6379/0" -``` - -```yaml teable-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: teable - namespace: teable # Replace with your namespace -spec: - replicas: 1 # Configure as needed - selector: - matchLabels: - app: teable - template: - metadata: - labels: - app: teable - spec: - # Add initContainers for database migration - initContainers: - - name: db-migrate - image: ghcr.io/teableio/teable:latest - args: - - migrate-only - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - requests: - cpu: 100m - memory: 102Mi - limits: - cpu: 1000m - memory: 1024Mi - containers: - - name: teable - image: ghcr.io/teableio/teable:latest - args: - - skip-migrate - ports: - - containerPort: 3000 - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - requests: - cpu: 200m - memory: 400Mi - limits: - cpu: 2000m - memory: 4096Mi - startupProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 10 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 30 - successThreshold: 1 - livenessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 30 - periodSeconds: 30 - timeoutSeconds: 5 - failureThreshold: 3 - successThreshold: 1 - readinessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 15 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 3 - successThreshold: 1 -``` - -```yaml teable-service.yaml -apiVersion: v1 -kind: Service -metadata: - name: teable - namespace: teable # Replace with your namespace -spec: - ports: - - port: 3000 - targetPort: 3000 - selector: - app: teable -``` - -```yaml teable-ingress.yaml (Optional) -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: teable - namespace: teable # Replace with your namespace - # Example using nginx, if using other ingress class, please replace - annotations: - nginx.ingress.kubernetes.io/proxy-body-size: "100m" - nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" - nginx.ingress.kubernetes.io/proxy-send-timeout: "600" - nginx.ingress.kubernetes.io/proxy-read-timeout: "600" - nginx.ingress.kubernetes.io/proxy-request-buffering: "off" -spec: - ingressClassName: nginx # Change to your ingress class if different - rules: - - host: your-domain.com # Replace with your domain - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: teable - port: - number: 3000 - tls: - - hosts: - - your-domain.com # Replace with your domain - secretName: your-tls-secret -``` - - -Do **NOT** add `nginx.ingress.kubernetes.io/rewrite-target` annotation. This will break Teable's internal routing. - - - -## Deployment Steps - - -Before deploying Teable, ensure that: -1. MinIO buckets (`teable-pub` and `teable-pvt`) are created -2. Public bucket has anonymous read access enabled -3. PostgreSQL database `teable` exists -4. Redis is accessible - - -1. Create namespace (if not exists): -```sh -kubectl create namespace teable -``` - -2. Create configuration and secrets: -```sh -kubectl apply -f teable-config.yaml -kubectl apply -f teable-secrets.yaml -``` - -3. Deploy application: -```sh -kubectl apply -f teable-deployment.yaml -kubectl apply -f teable-service.yaml -kubectl apply -f teable-ingress.yaml # Optional, if using Ingress -``` - -4. Verify deployment: -```sh -# Check Pod status -kubectl get pods -n teable -l app=teable - -# Wait for pods to be ready -kubectl rollout status deployment/teable -n teable - -# View application logs -kubectl logs -n teable -l app=teable -``` - -### Configuration Notes - -1. Sensitive Information Management - - All password, secret-related information should be managed through Secrets - -2. Database Configuration - - Ensure that the teable database exists in the database - - The database user needs to have appropriate permissions - - It's recommended to use a connection pool to manage database connections - -3. MinIO Configuration - - Ensure that the storage bucket exists and the permissions are correct, one public bucket, one private bucket - - The public bucket needs to be completely publicly readable - - The internal and external access address configuration is correct - -4. Redis Configuration - - It's recommended to enable Redis persistence - - Configure appropriate memory limits - - Consider using Redis cluster to improve availability - -5. Security Recommendations - - Use strong passwords and secrets - - Enable TLS/SSL encryption - - Regularly update certificates - - Limit network access scope - -6. Resource Configuration - - Adjust resource limits based on actual load - - Monitor resource usage - - Configure appropriate health check parameters - -### Troubleshooting - -#### Common Issues - -| Error | Cause | Solution | -|-------|-------|----------| -| `ECONNREFUSED` to MinIO | MinIO internal endpoint incorrect or unreachable | Verify `BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT` uses correct format: `..svc.cluster.local` | -| `Bucket not found` | MinIO buckets not created | Create buckets using `mc mb` command before deployment | -| `Access Denied` on file upload | MinIO credentials incorrect | Verify `BACKEND_STORAGE_MINIO_ACCESS_KEY` and `SECRET_KEY` | -| `Page not found` on web access | Ingress `rewrite-target` configured | Remove `rewrite-target` annotation from Ingress | -| Pod stuck in `CrashLoopBackOff` | Backend failed to start | Check logs for MinIO, database, or Redis connectivity issues | - -#### Diagnostic Commands - -1. Check Pod status: -```sh -kubectl describe pod -n teable -l app=teable -``` - -2. View application logs: -```sh -kubectl logs -n teable -l app=teable -kubectl logs -n teable -l app=teable --previous # If pod crashed -``` - -3. Verify configuration: -```sh -kubectl describe configmap teable-config -n teable -kubectl describe secret teable-secrets -n teable -``` - -4. Check network connection: -```sh -kubectl exec -it -n teable -- curl -v localhost:3000/health -``` - -5. Test MinIO connectivity from inside the cluster: -```sh -kubectl run test-minio --rm -it --image=curlimages/curl --restart=Never -n teable -- \ - curl -v http://minio.teable.svc.cluster.local/minio/health/live -``` diff --git a/en/deploy/one-key.mdx b/en/deploy/one-key.mdx deleted file mode 100644 index 534485e3..00000000 --- a/en/deploy/one-key.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: "One-Click Cloud Deployment" -description: "Deploy Teable quickly using cloud provider templates, then subscribe to unlock paid features and activate your license." ---- - - -**Deprecated.** This page is no longer maintained and has been removed from the -docs navigation. For current deployment guidance, start from -[Choose Your Path](/en/deploy/choose). The templates below are currently not functional. - - - -## One-Click Cloud Deployment - -One-click templates help you bootstrap a Teable environment quickly. After deployment, if you want paid features (for self-hosted plans), you should **subscribe** and **activate your license** using your Instance ID. - -## Step 1: Deploy with a template - -Choose a template below and deploy. - -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/NtH5uD?referralCode=rE4BjB) - -[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/QF8695) - -[![Deploy to RepoCloud](https://d16t0pc4846x52.cloudfront.net/deploylobe.svg)](https://repocloud.io/details/?app_id=273) - -[![Deploy on Elestio](https://elest.io/images/logos/deploy-to-elestio-btn.png)](https://elest.io/open-source/teable) - -## Step 2: Subscribe (paid features) - -If you are deploying **self-hosted** and need paid features, subscribe with your **Instance ID**: - -1. Open the Admin Panel in your deployed instance and copy the **Instance ID** -2. Go to the self-hosted pricing page and subscribe: `https://app.teable.ai/public/pricing?host=self-hosted` -3. After subscribing, you will get a **License Key** - -Then activate it in your instance: - -- Documentation: [Subscribe and Activate License](/en/deploy/activate) - - -If you are using **Teable Cloud** (hosted by Teable), manage your plan in-app: -see [Billing & Subscription](/en/basic/space/billing). - - -## Need help? - -If you have questions about subscription or licensing, contact `support@teable.ai`. diff --git a/en/deploy/production-overview.mdx b/en/deploy/production-overview.mdx deleted file mode 100644 index ba9a0cde..00000000 --- a/en/deploy/production-overview.mdx +++ /dev/null @@ -1,186 +0,0 @@ ---- -title: "Overview" -description: "Architecture, sizing, and deployment path selection for production Teable deployments" ---- - - -**Deprecated.** This page is no longer maintained and has been removed from the -docs navigation. For current deployment guidance, start from -[Choose Your Path](/en/deploy/choose). - - - - -**Recommended for:** Production deployments with 50+ users - - - -This page covers scaling the **standalone** Teable app. If you need AI -features or App Builder, deploy the -[full-featured platform](/en/deploy/agent-runtime) instead — it includes the -app plus the AI runtime plane. - - -## Architecture Overview - -Teable uses a **stateless application tier** with externalized state services. This design enables horizontal scaling and high availability. - -```mermaid -graph TB - subgraph "Application Tier (Stateless)" - App1["Teable Pod 1"] - App2["Teable Pod 2"] - AppN["Teable Pod N..."] - end - - subgraph "Data Tier (Stateful)" - DB["PostgreSQL
(Master + Replica)"] - Redis1["Redis
Queue & Cache"] - Redis2["Redis
Performance Cache"] - S3["S3 / MinIO
Object Storage"] - end - - App1 --> DB - App2 --> DB - AppN --> DB - App1 --> Redis1 - App1 --> Redis2 - App1 --> S3 - - style App1 fill:#0D9373,stroke:#0a7a5e,color:#fff - style App2 fill:#0D9373,stroke:#0a7a5e,color:#fff - style AppN fill:#0D9373,stroke:#0a7a5e,color:#fff - style DB fill:#336791,stroke:#264d73,color:#fff - style Redis1 fill:#dc382d,stroke:#a82a22,color:#fff - style Redis2 fill:#dc382d,stroke:#a82a22,color:#fff - style S3 fill:#ff9900,stroke:#cc7a00,color:#fff -``` - -### Components - -| Component | Purpose | Managed Service Examples | -|-----------|---------|-------------------------| -| **Teable App** | Stateless web application | K8s, AWS ECS, Azure App Service, GCP Cloud Run | -| **PostgreSQL** | Primary data storage | AWS RDS, Azure PostgreSQL, Google Cloud SQL | -| **Redis (Queue)** | Background jobs & cache | ElastiCache, Azure Cache, Memorystore | -| **Redis (Perf)** | Performance cache for reads | Same or separate Redis instance | -| **Object Storage** | Files & attachments | AWS S3, MinIO, any S3-compatible | - - -**Key insight**: The Teable application holds no persistent state. You can scale horizontally by adding pods, and any pod can handle any request. - - ---- - -## Resource Recommendations - - -These are **production** recommendations from the Teable engineering team. For evaluation/testing, see [Docker Quick Start](/en/deploy/docker). - - -### Application Tier (Teable Pods) - -| Setting | Recommended Value | -|---------|-------------------| -| CPU per pod | 2 vCPU | -| Memory per pod | 4 GB | -| Minimum replicas | 2 | -| Auto-scale trigger | 50% CPU utilization | - - -Use Kubernetes with Horizontal Pod Autoscaler (HPA), or equivalent auto-scaling on AWS ECS / Azure App Service / GCP Cloud Run. - - -### Database (PostgreSQL) - -| Setting | Recommended Value | -|---------|-------------------| -| CPU | 4 vCPU | -| Memory | 16 GB | -| Topology | 1 Master + 1 Replica | -| Backup | Daily automated | -| Version | 15+ recommended | - - -Always deploy with a replica for high availability. Use managed database services for automatic failover. - - -### Cache (Redis) - -Teable uses **two separate Redis instances** for different purposes: - -| Purpose | Environment Variable | CPU | Memory | -|---------|---------------------|-----|--------| -| Queue & Cache | `BACKEND_CACHE_REDIS_URI` | 1 vCPU | 2 GB | -| Performance Cache | `BACKEND_PERFORMANCE_CACHE` | 2 vCPU | 4 GB | - - -The Performance Cache (`BACKEND_PERFORMANCE_CACHE`) is optional but **strongly recommended** for multi-user collaboration. It significantly improves read performance in busy workspaces. - - -### Object Storage - -- **Type**: S3-compatible (AWS S3, MinIO, etc.) -- **Buckets**: 2 required (public + private) -- **Capacity**: Scales automatically with managed S3 - -See [Object Storage Guide](/en/deploy/storage) for bucket configuration details. - ---- - -## Scaling Strategy - -| Component | Scaling Type | When to Scale | -|-----------|--------------|---------------| -| Teable App | **Automatic** (horizontal) | CPU > 50% (add pods) | -| PostgreSQL | **Manual** (vertical) | CPU > 70% sustained | -| Redis | **Manual** (vertical) | Memory > 80% or CPU > 60% | - - -**Application tier**: Stateless design allows automatic horizontal scaling. Kubernetes HPA or cloud auto-scaling handles this automatically based on CPU metrics. - -**Database & Redis**: These are stateful services where vertical scaling (bigger instance) is simpler and safer than horizontal scaling. Monitor metrics and resize when thresholds are exceeded. - - ---- - -## Minimum vs. Production Comparison - -| | Minimum (PoC) | Production | -|---|---------------|------------| -| Teable App | 1 instance, 2c4g | 2+ pods, 2c4g each, auto-scaling | -| PostgreSQL | 2c4g, single instance | 4c16g, 1 master + 1 replica | -| Redis | 1 instance, 1c2g | 2 instances (queue + performance) | -| Storage | S3-compatible object storage | S3-compatible object storage | -| Availability | None (single point of failure) | High availability with redundancy | - - -The "Minimum" setup is for **evaluation only**. It has no redundancy—data loss is possible if the server fails. - - ---- - -## Next Steps: Choose Your Platform - - - - The app plus the AI runtime plane on any K8s cluster, via one Helm chart. - - - ECS + RDS + ElastiCache + S3. Recommended for 50+ users. - - - App Service + PostgreSQL + Redis + S3-compatible storage. - - - Cloud Run + Cloud SQL + Memorystore + Cloud Storage. - - - ---- - -## Related Documentation - -- [Environment Variables Reference](/en/deploy/env) — All configuration options -- [Object Storage Setup](/en/deploy/storage) — S3 bucket configuration -- [License Activation](/en/deploy/activate) — Activate your self-hosted license diff --git a/en/deploy/storage.mdx b/en/deploy/storage.mdx deleted file mode 100644 index 9e9c9312..00000000 --- a/en/deploy/storage.mdx +++ /dev/null @@ -1,129 +0,0 @@ ---- -title: "Object Storage (S3-compatible)" -description: "Production storage guidance for Teable: public/private buckets, permissions, and CORS" ---- - - -**Deprecated.** This page is no longer maintained and has been removed from the -docs navigation. For current deployment guidance, start from -[Choose Your Path](/en/deploy/choose). Storage now ships preconfigured with the deployment assets; the full-featured platform manages three buckets (public, private, and build artifacts) for you. - - - - -For production deployments, use **S3-compatible object storage** (such as S3, MinIO, OSS/OBS/COS with S3-compatible endpoints, or another S3-compatible service) with **public/private dual buckets**. - - -## Why two buckets? - -Teable stores different types of files in different buckets: - -- **Public bucket**: avatars, form cover images, and other assets that must be publicly accessible. -- **Private bucket**: attachments, app files, record-history cold parts, and other content that should be protected. - -## Required environment variables - -At minimum, configure: - -- **Provider** - - `BACKEND_STORAGE_PROVIDER`: `s3` or `minio` -- **Buckets** - - `BACKEND_STORAGE_PUBLIC_BUCKET` - - `BACKEND_STORAGE_PRIVATE_BUCKET` -- **Public access base URL** - - `STORAGE_PREFIX`: should be the **public bucket** access base URL (or CDN in front of it) - -### S3 provider - -```bash -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY=***REDACTED*** -BACKEND_STORAGE_S3_SECRET_KEY=***REDACTED*** - -BACKEND_STORAGE_PUBLIC_BUCKET=your-public-bucket -BACKEND_STORAGE_PRIVATE_BUCKET=your-private-bucket - -# Public bucket base URL (or CDN) -STORAGE_PREFIX=https://your-public-bucket.s3.us-west-2.amazonaws.com -``` - -### MinIO provider - -```bash -BACKEND_STORAGE_PROVIDER=minio -BACKEND_STORAGE_MINIO_ENDPOINT=minio.example.com -BACKEND_STORAGE_MINIO_PORT=443 -BACKEND_STORAGE_MINIO_USE_SSL=true -BACKEND_STORAGE_MINIO_ACCESS_KEY=***REDACTED*** -BACKEND_STORAGE_MINIO_SECRET_KEY=***REDACTED*** - -BACKEND_STORAGE_PUBLIC_BUCKET=public -BACKEND_STORAGE_PRIVATE_BUCKET=private -STORAGE_PREFIX=https://minio.example.com -``` - -## Public bucket requirements (must-do) - -### 1) Public read access - -The **public bucket must allow public read** for objects. - -If you use AWS S3, you can set a bucket policy like: - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "PublicReadGetObject", - "Effect": "Allow", - "Principal": "*", - "Action": "s3:GetObject", - "Resource": "arn:aws:s3:::your-public-bucket/*" - } - ] -} -``` - - -AWS S3 may require you to adjust **Block Public Access** settings for the public bucket. Please follow your organization’s security policy. - - -### 2) CORS (allow any origin) - -For browser access, configure CORS on the **public bucket** to allow cross-origin requests. - -AWS S3 example: - -```json -[ - { - "AllowedHeaders": ["*"], - "AllowedMethods": ["GET", "HEAD"], - "AllowedOrigins": ["*"], - "ExposeHeaders": ["ETag", "x-amz-request-id", "x-amz-id-2"], - "MaxAgeSeconds": 3000 - } -] -``` - -If you use **direct browser uploads** (presigned PUT), you may also need `PUT` in `AllowedMethods`. - -## Private bucket guidance - -- Keep the **private bucket** non-public. -- Teable serves private objects through **signed URLs** or backend reads, depending on the feature. -- Record-history cold storage writes compressed history parts to the private bucket. Keep bucket backup and retention policies aligned with your record-history requirements. -- If your provider supports “bucket endpoint” for private access, you can configure: - - `BACKEND_STORAGE_PRIVATE_BUCKET_ENDPOINT` (optional) - -## Azure / GCP note - -Teable uses **S3 APIs** for object storage. Azure Blob Storage and Google Cloud Storage are **not S3-native**. - -- If you are on **Azure** or **GCP**, you can still use `BACKEND_STORAGE_PROVIDER=s3` as long as you point to an S3 endpoint (for example, AWS S3) and your Teable runtime can reach it. -- Or use an **S3-compatible** storage: - - MinIO (self-managed) as the S3 endpoint - - Another managed S3-compatible service (your choice) diff --git a/images/selfhosted/database-connection/2026-05-29-database-connection-overview-en.png b/images/selfhosted/database-connection/2026-05-29-database-connection-overview-en.png deleted file mode 100644 index a8bb5e1e14a8424c9279bd2baf6344ecf78a75f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62649 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%0Dh4KEQ(0 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 diff --git a/images/selfhosted/database-connection/2026-05-29-database-connection-overview-zh.png b/images/selfhosted/database-connection/2026-05-29-database-connection-overview-zh.png deleted file mode 100644 index ff24d8ad85311f8c080208aaa626f3ace7a9993c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46881 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 IDe&h10TGQnY5)KL diff --git a/zh/deploy/aws.mdx b/zh/deploy/aws.mdx deleted file mode 100644 index 1fc44923..00000000 --- a/zh/deploy/aws.mdx +++ /dev/null @@ -1,388 +0,0 @@ ---- -title: "在 AWS 上部署" -description: "分步指引:在 AWS 上使用 ECS、RDS PostgreSQL、ElastiCache 与 S3 部署 Teable" ---- - - -**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 -[选择部署方式](/zh/deploy/choose)开始。 - - - - -**推荐场景**:50+ 用户的生产环境 - - -## 架构概览 - -```mermaid -graph TB - ECS["AWS ECS
(Fargate 或 EC2)
Teable 应用: 4+ 副本
Sandbox: 2+ 副本 (可选)"] - - RDS["Amazon RDS
Aurora PostgreSQL"] - ElastiCache["Amazon ElastiCache
(Valkey/Redis)
队列 & 缓存"] - S3["Amazon S3
Public + Private 双桶"] - - ECS --> RDS - ECS --> ElastiCache - ECS --> S3 - - style ECS fill:#ff9900,stroke:#cc7a00,color:#fff - style RDS fill:#527fff,stroke:#3d5fd9,color:#fff - style ElastiCache fill:#c925d1,stroke:#a01ba8,color:#fff - style S3 fill:#569a31,stroke:#3f7224,color:#fff -``` - -## 前置要求 - -- 已安装并配置 AWS CLI -- AWS 账号具备 ECS、RDS、ElastiCache、S3、IAM 权限 -- Docker 镜像仓库访问权(或 ECR) - ---- - -## 步骤 1:创建 AWS 资源 - -### 1.1 创建 RDS PostgreSQL - -```bash -aws rds create-db-instance \ - --db-instance-identifier teable-db \ - --db-instance-class db.t3.medium \ - --engine postgres \ - --engine-version 16 \ - --master-username teableadmin \ - --master-user-password '<你的强密码>' \ - --allocated-storage 100 \ - --vpc-security-group-ids sg-xxxxxx \ - --db-subnet-group-name your-subnet-group \ - --publicly-accessible false -``` - - -等待实例状态变为 `available`。获取 endpoint: -```bash -aws rds describe-db-instances \ - --db-instance-identifier teable-db \ - --query 'DBInstances[0].Endpoint.Address' --output text -``` - - -### 1.2 创建 ElastiCache Redis(用于队列) - -```bash -aws elasticache create-cache-cluster \ - --cache-cluster-id teable-cache \ - --engine redis \ - --cache-node-type cache.t3.small \ - --num-cache-nodes 1 \ - --engine-version 7.0 \ - --security-group-ids sg-xxxxxx \ - --cache-subnet-group-name your-subnet-group -``` - - -获取 endpoint: -```bash -aws elasticache describe-cache-clusters \ - --cache-cluster-id teable-cache \ - --show-cache-node-info \ - --query 'CacheClusters[0].CacheNodes[0].Endpoint.Address' --output text -``` - - -### 1.3 创建 S3 bucket(public + private) - -```bash -# 创建 public bucket -aws s3api create-bucket \ - --bucket teable-public-<你的唯一后缀> \ - --region us-west-2 \ - --create-bucket-configuration LocationConstraint=us-west-2 - -# 创建 private bucket -aws s3api create-bucket \ - --bucket teable-private-<你的唯一后缀> \ - --region us-west-2 \ - --create-bucket-configuration LocationConstraint=us-west-2 -``` - -**必做:配置 public bucket** - -1. **Public read 策略**: - -```bash -cat > public-bucket-policy.json </*" - } - ] -} -EOF - -aws s3api put-bucket-policy \ - --bucket teable-public-<你的唯一后缀> \ - --policy file://public-bucket-policy.json -``` - -2. **关闭 public bucket 的"阻止公共访问"**: - -```bash -aws s3api put-public-access-block \ - --bucket teable-public-<你的唯一后缀> \ - --public-access-block-configuration \ - BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false -``` - -3. **CORS 配置**(允许任意跨域): - -```bash -cat > cors.json < \ - --cors-configuration file://cors.json -``` - - -**安全提醒**:public bucket 必须允许公开读。请确保符合你们公司的安全规范。private bucket 保持私有即可。 - - -### 1.4 创建 IAM 用户/角色用于 S3 访问 - -```bash -# 创建 IAM 用户 -aws iam create-user --user-name teable-s3-user - -# 附加 S3 完全访问策略(或创建自定义策略) -aws iam attach-user-policy \ - --user-name teable-s3-user \ - --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess - -# 创建访问密钥 -aws iam create-access-key --user-name teable-s3-user -``` - - -保存输出中的 `AccessKeyId` 和 `SecretAccessKey`。 - - ---- - -## 步骤 2:准备环境变量 - -创建 `.env` 文件(或 ECS 任务定义的环境变量): - -```bash -# Core -PUBLIC_ORIGIN=https://teable.yourcompany.com -SECRET_KEY=<生成32位随机字符串> - -# Database -PRISMA_DATABASE_URL=postgresql://teableadmin:@:5432/teable - -# Redis(队列依赖) -BACKEND_CACHE_PROVIDER=redis -BACKEND_CACHE_REDIS_URI=redis://:6379/0 - -# 性能缓存(可选;可指向同一个 Redis 或单独集群) -BACKEND_PERFORMANCE_CACHE=redis://:6379/0 - -# Storage(S3 双桶) -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY=<步骤1.4的access-key> -BACKEND_STORAGE_S3_SECRET_KEY=<步骤1.4的secret-key> -BACKEND_STORAGE_PUBLIC_BUCKET=teable-public-<你的唯一后缀> -BACKEND_STORAGE_PRIVATE_BUCKET=teable-private-<你的唯一后缀> -STORAGE_PREFIX=https://teable-public-<你的唯一后缀>.s3.us-west-2.amazonaws.com - -``` - - -生成强 `SECRET_KEY`: -```bash -openssl rand -base64 32 -``` - - ---- - -## 步骤 3:部署到 ECS - -### 方案 A:使用 ECS Fargate(推荐,简单) - -1. **创建 ECS 集群**: - -```bash -aws ecs create-cluster --cluster-name teable-cluster -``` - -2. **创建任务定义** (`teable-task.json`): - -```json -{ - "family": "teable", - "networkMode": "awsvpc", - "requiresCompatibilities": ["FARGATE"], - "cpu": "2048", - "memory": "4096", - "containerDefinitions": [ - { - "name": "teable", - "image": "ghcr.io/teableio/teable:latest", - "portMappings": [ - { - "containerPort": 3000, - "protocol": "tcp" - } - ], - "environment": [ - {"name": "PUBLIC_ORIGIN", "value": "https://teable.yourcompany.com"}, - {"name": "SECRET_KEY", "value": "***REDACTED***"}, - {"name": "PRISMA_DATABASE_URL", "value": "postgresql://..."}, - {"name": "BACKEND_CACHE_PROVIDER", "value": "redis"}, - {"name": "BACKEND_CACHE_REDIS_URI", "value": "redis://..."}, - {"name": "BACKEND_STORAGE_PROVIDER", "value": "s3"}, - {"name": "BACKEND_STORAGE_S3_REGION", "value": "us-west-2"}, - {"name": "BACKEND_STORAGE_S3_ENDPOINT", "value": "https://s3.us-west-2.amazonaws.com"}, - {"name": "BACKEND_STORAGE_S3_ACCESS_KEY", "value": "***"}, - {"name": "BACKEND_STORAGE_S3_SECRET_KEY", "value": "***"}, - {"name": "BACKEND_STORAGE_PUBLIC_BUCKET", "value": "teable-public-xxx"}, - {"name": "BACKEND_STORAGE_PRIVATE_BUCKET", "value": "teable-private-xxx"}, - {"name": "STORAGE_PREFIX", "value": "https://teable-public-xxx.s3.us-west-2.amazonaws.com"} - ], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-group": "/ecs/teable", - "awslogs-region": "us-west-2", - "awslogs-stream-prefix": "teable" - } - } - } - ] -} -``` - -3. **注册任务定义**: - -```bash -aws ecs register-task-definition --cli-input-json file://teable-task.json -``` - -4. **创建 ECS 服务**: - -```bash -aws ecs create-service \ - --cluster teable-cluster \ - --service-name teable-service \ - --task-definition teable \ - --desired-count 2 \ - --launch-type FARGATE \ - --network-configuration "awsvpcConfiguration={subnets=[subnet-xxx,subnet-yyy],securityGroups=[sg-xxx],assignPublicIp=ENABLED}" \ - --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:...,containerName=teable,containerPort=3000" -``` - - -你需要单独创建 Application Load Balancer (ALB) 和目标组,然后在 `--load-balancers` 中引用。 - - ---- - -## 步骤 4:验证部署 - -1. **检查 ECS 服务状态**: - -```bash -aws ecs describe-services \ - --cluster teable-cluster \ - --services teable-service -``` - -2. **查看日志**: - -```bash -aws logs tail /ecs/teable --follow -``` - -3. **访问健康检查**: - -```bash -curl https://teable.yourcompany.com/health -``` - -预期返回: -```json -{"status":"ok"} -``` - -4. **浏览器访问 Teable**:`https://teable.yourcompany.com` - ---- - -## 故障排查 - -### 数据库连接错误 - -- 验证 RDS 安全组允许来自 ECS 任务的流量 -- 检查 `PRISMA_DATABASE_URL` 格式:`postgresql://user:pass@endpoint:5432/dbname` - -### Redis 连接错误 - -- 验证 ElastiCache 安全组允许来自 ECS 的流量 -- 使用 **主节点 endpoint**(不是只读副本) - -### S3 访问错误 - -- 验证 IAM 用户具有 `s3:GetObject`、`s3:PutObject`、`s3:DeleteObject` 权限 -- 检查 bucket 名称在 `BACKEND_STORAGE_PUBLIC_BUCKET` / `BACKEND_STORAGE_PRIVATE_BUCKET` 中完全匹配 -- 确保 public bucket 已配置 public read 策略与 CORS - -### 容器启动失败 - -- 检查 CloudWatch Logs:`/ecs/teable` -- 验证所有必需环境变量已设置 -- 确保 `SECRET_KEY` 已设置(不为空) - ---- - -## 生产环境最佳实践 - - -详细的生产环境建议(包括配置、高可用、扩展策略)请参考 [私有化部署概览](/zh/deploy/production-overview)。 - - -**AWS 特定建议**: -- 使用 AWS Secrets Manager 存储敏感值 -- 启用 RDS 静态加密 -- 使用 VPC endpoint 访问 S3/ECR 避免公网流量 -- 启用 CloudWatch Container Insights for ECS - ---- - -## 相关文档 - -- [私有化部署概览](/zh/deploy/production-overview) — 架构、配置与扩展 -- [环境变量参考](/zh/deploy/env) -- [对象存储(S3-compatible)](/zh/deploy/storage) diff --git a/zh/deploy/azure.mdx b/zh/deploy/azure.mdx deleted file mode 100644 index 61fc6020..00000000 --- a/zh/deploy/azure.mdx +++ /dev/null @@ -1,826 +0,0 @@ ---- -title: "在 Azure 上部署" -description: "分步指引:在 Azure 上使用 App Service 或 AKS 部署 Teable,搭配 PostgreSQL Flexible Server、Azure Cache for Redis 与 S3-compatible 存储" ---- - - -**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 -[选择部署方式](/zh/deploy/choose)开始。 - - - - -**推荐场景**:50+ 用户的生产环境 - - -## 架构概览 - -```mermaid -graph TB - AppService["Azure App Service
(Linux 容器)
Teable 应用
(按需扩展)"] - - DB["Azure Database for
PostgreSQL Flexible Server"] - Redis["Azure Cache for Redis
(队列 & 缓存)"] - S3["S3-compatible 存储
(AWS S3 或 MinIO)
Public + Private 双桶"] - - AppService --> DB - AppService --> Redis - AppService --> S3 - - style AppService fill:#0078d4,stroke:#005a9e,color:#fff - style DB fill:#0078d4,stroke:#005a9e,color:#fff - style Redis fill:#0078d4,stroke:#005a9e,color:#fff - style S3 fill:#ff9900,stroke:#cc7a00,color:#fff -``` - - -**重要**:Teable 需要 **S3-compatible 存储**。你可以选择: -- **AWS S3**(跨云方案) -- **MinIO**(在 Azure VM/AKS 自建) -- 其他 S3-compatible 服务 - -Azure Blob Storage **不直接支持**,因为它使用不同的 API。 - - -## 前置要求 - -- 已安装并登录 Azure CLI -- Azure 订阅具备 App Service、PostgreSQL、Redis 权限 -- 具备 **S3-compatible 存储**访问权(例如 AWS S3 账号或 MinIO 部署) - ---- - -## 步骤 1:创建 Azure 资源 - -### 1.1 创建资源组 - -```bash -az group create \ - --name teable-rg \ - --location eastus -``` - -### 1.2 创建 PostgreSQL Flexible Server - -```bash -az postgres flexible-server create \ - --resource-group teable-rg \ - --name teable-db \ - --location eastus \ - --admin-user teableadmin \ - --admin-password '<你的强密码>' \ - --sku-name Standard_B2s \ - --tier Burstable \ - --storage-size 128 \ - --version 16 \ - --public-access 0.0.0.0-255.255.255.255 -``` - - -等待预配完成。获取连接字符串: -```bash -az postgres flexible-server show \ - --resource-group teable-rg \ - --name teable-db \ - --query "fullyQualifiedDomainName" --output tsv -``` -结果:`teable-db.postgres.database.azure.com` - - -### 1.3 创建 Azure Cache for Redis - -```bash -az redis create \ - --resource-group teable-rg \ - --name teable-cache \ - --location eastus \ - --sku Basic \ - --vm-size c0 \ - --minimum-tls-version 1.2 -``` - - -获取访问密钥: -```bash -az redis list-keys \ - --resource-group teable-rg \ - --name teable-cache \ - --query "primaryKey" --output tsv -``` - - ---- - -## 步骤 2:设置 S3-compatible 存储 - -由于 Azure Blob Storage 不兼容 S3,请选择以下方案之一: - -### 方案 A:使用 AWS S3(跨云) - -如果你已有 AWS 访问权或不想管理 MinIO,这是最简单的方案。 - -1. **在 AWS 上创建 S3 bucket**(参见 [AWS 部署指引](/zh/deploy/aws) 步骤 1.3-1.4) - - Public bucket:`teable-public-<唯一后缀>` - - Private bucket:`teable-private-<唯一后缀>` - -2. **配置 public bucket**(参见 [对象存储指引](/zh/deploy/storage)): - - 启用 public read 访问 - - 配置 CORS 允许任意跨域 - -你将使用这些环境变量: -```bash -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY= -BACKEND_STORAGE_S3_SECRET_KEY= -BACKEND_STORAGE_PUBLIC_BUCKET=teable-public-<后缀> -BACKEND_STORAGE_PRIVATE_BUCKET=teable-private-<后缀> -STORAGE_PREFIX=https://teable-public-<后缀>.s3.us-west-2.amazonaws.com -``` - -### 方案 B:在 Azure 上部署 MinIO - -如果希望所有资源都在 Azure,可部署 MinIO 作为 S3-compatible 网关。 - -**推荐镜像**:`minio/minio:RELEASE.2025-04-22T22-12-26Z` - -**快速设置(Azure VM)**: - -1. 创建 VM 并安装 MinIO: -```bash -wget https://dl.min.io/server/minio/release/linux-amd64/minio -chmod +x minio -sudo mv minio /usr/local/bin/ - -# 启动 MinIO -export MINIO_ROOT_USER=admin -export MINIO_ROOT_PASSWORD=<强密码> -minio server /data --console-address ":9001" -``` - -2. 通过 MinIO 控制台(端口 9001)或 CLI 创建 bucket: -```bash -mc alias set myminio http://your-vm-ip:9000 admin <密码> -mc mb myminio/public -mc mb myminio/private -mc policy set download myminio/public -``` - -你将使用这些环境变量: -```bash -BACKEND_STORAGE_PROVIDER=minio -BACKEND_STORAGE_MINIO_ENDPOINT= -BACKEND_STORAGE_MINIO_PORT=443 -BACKEND_STORAGE_MINIO_USE_SSL=true -BACKEND_STORAGE_MINIO_ACCESS_KEY=admin -BACKEND_STORAGE_MINIO_SECRET_KEY=<密码> -BACKEND_STORAGE_PUBLIC_BUCKET=public -BACKEND_STORAGE_PRIVATE_BUCKET=private -STORAGE_PREFIX=https:// -``` - ---- - -## 步骤 3:准备环境变量 - -创建文件 `app-settings.txt`,包含所有必需变量: - -```bash -# Core -PUBLIC_ORIGIN=https://teable-app.azurewebsites.net -SECRET_KEY=<生成32位随机字符串> - -# Database -PRISMA_DATABASE_URL=postgresql://teableadmin:<密码>@teable-db.postgres.database.azure.com:5432/postgres?sslmode=require - -# Redis(队列依赖) -BACKEND_CACHE_PROVIDER=redis -BACKEND_CACHE_REDIS_URI=rediss://:@teable-cache.redis.cache.windows.net:6380/0 - -# 性能缓存(可选;可指向同一个 Redis) -BACKEND_PERFORMANCE_CACHE=rediss://:@teable-cache.redis.cache.windows.net:6380/0 - -# Storage(S3-compatible) -# 方案 A(AWS S3): -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY= -BACKEND_STORAGE_S3_SECRET_KEY= -BACKEND_STORAGE_PUBLIC_BUCKET=teable-public-<后缀> -BACKEND_STORAGE_PRIVATE_BUCKET=teable-private-<后缀> -STORAGE_PREFIX=https://teable-public-<后缀>.s3.us-west-2.amazonaws.com - -# 方案 B(MinIO): -# BACKEND_STORAGE_PROVIDER=minio -# BACKEND_STORAGE_MINIO_ENDPOINT= -# BACKEND_STORAGE_MINIO_PORT=443 -# BACKEND_STORAGE_MINIO_USE_SSL=true -# BACKEND_STORAGE_MINIO_ACCESS_KEY=admin -# BACKEND_STORAGE_MINIO_SECRET_KEY=<密码> -# BACKEND_STORAGE_PUBLIC_BUCKET=public -# BACKEND_STORAGE_PRIVATE_BUCKET=private -# STORAGE_PREFIX=https:// -``` - - -生成强 `SECRET_KEY`: -```bash -openssl rand -base64 32 -``` - - ---- - -## 步骤 4:部署到 Azure App Service - -### 4.1 创建 App Service Plan - -```bash -az appservice plan create \ - --resource-group teable-rg \ - --name teable-plan \ - --location eastus \ - --is-linux \ - --sku P1v3 -``` - -### 4.2 创建 Web App(容器方式) - -```bash -az webapp create \ - --resource-group teable-rg \ - --plan teable-plan \ - --name teable-app \ - --deployment-container-image-name ghcr.io/teableio/teable:latest -``` - -### 4.3 配置容器设置 - -```bash -az webapp config container set \ - --resource-group teable-rg \ - --name teable-app \ - --docker-custom-image-name ghcr.io/teableio/teable:latest \ - --docker-registry-server-url https://ghcr.io -``` - -### 4.4 设置环境变量 - -```bash -# 将 app-settings.txt 转为 JSON 格式并应用 -az webapp config appsettings set \ - --resource-group teable-rg \ - --name teable-app \ - --settings \ - WEBSITES_PORT=3000 \ - PUBLIC_ORIGIN="https://teable-app.azurewebsites.net" \ - SECRET_KEY="<你的secret>" \ - PRISMA_DATABASE_URL="postgresql://..." \ - BACKEND_CACHE_PROVIDER="redis" \ - BACKEND_CACHE_REDIS_URI="rediss://..." \ - BACKEND_PERFORMANCE_CACHE="rediss://..." \ - BACKEND_STORAGE_PROVIDER="s3" \ - BACKEND_STORAGE_S3_REGION="us-west-2" \ - BACKEND_STORAGE_S3_ENDPOINT="https://s3.us-west-2.amazonaws.com" \ - BACKEND_STORAGE_S3_ACCESS_KEY="***" \ - BACKEND_STORAGE_S3_SECRET_KEY="***" \ - BACKEND_STORAGE_PUBLIC_BUCKET="teable-public-xxx" \ - BACKEND_STORAGE_PRIVATE_BUCKET="teable-private-xxx" \ - STORAGE_PREFIX="https://teable-public-xxx.s3.us-west-2.amazonaws.com" -``` - -### 4.5 配置健康检查 - -```bash -az webapp config set \ - --resource-group teable-rg \ - --name teable-app \ - --generic-configurations '{"healthCheckPath": "/health"}' -``` - -### 4.6 重启应用 - -```bash -az webapp restart \ - --resource-group teable-rg \ - --name teable-app -``` - ---- - -## 步骤 5:验证部署 - -1. **检查应用状态**: - -```bash -az webapp show \ - --resource-group teable-rg \ - --name teable-app \ - --query "state" --output tsv -``` - -预期:`Running` - -2. **查看日志**: - -```bash -az webapp log tail \ - --resource-group teable-rg \ - --name teable-app -``` - -3. **测试健康检查**: - -```bash -curl https://teable-app.azurewebsites.net/health -``` - -预期返回: -```json -{"status":"ok"} -``` - -4. **浏览器访问 Teable**:`https://teable-app.azurewebsites.net` - ---- - -## 故障排查 - -### 数据库连接错误 - -- 验证 PostgreSQL 防火墙允许 Azure 服务 -- 检查连接字符串包含 `?sslmode=require` -- 确保数据库名称正确(默认是 `postgres`,不是 `teable`) - -### Redis 连接错误 - -- 使用 `rediss://`(双's')进行 TLS 连接 -- 使用端口 **6380**(不是 6379)连接 Azure Cache for Redis -- 验证访问密钥正确 - -### S3 访问错误(方案 A:AWS S3) - -- 验证 AWS 凭据正确 -- 检查 bucket 名称完全匹配 -- 确保 public bucket 已配置 public read 策略与 CORS -- 测试 S3 从 Azure 的访问:`curl https://s3.us-west-2.amazonaws.com` 应该可用 - -### 容器启动失败 - -- 检查日志:`az webapp log tail ...` -- 验证所有必需环境变量已设置 -- 确保 `WEBSITES_PORT=3000` 已设置 - ---- - -## 生产环境最佳实践 - - -详细的生产环境建议(包括配置、高可用、扩展策略)请参考 [私有化部署概览](/zh/deploy/production-overview)。 - - -**Azure 特定建议**: -- 使用 Azure Key Vault 存储敏感值 -- 启用仅 HTTPS:`az webapp update --https-only true` -- 使用 VNet Integration 限制数据库/redis 访问 -- 启用 Application Insights 监控 - -**自定义域名配置**: -```bash -# 添加自定义域名 -az webapp config hostname add \ - --resource-group teable-rg \ - --webapp-name teable-app \ - --hostname teable.yourcompany.com - -# 创建托管 SSL 证书 -az webapp config ssl create \ - --resource-group teable-rg \ - --name teable-app \ - --hostname teable.yourcompany.com -``` - ---- - -## 替代方案:在 AKS 上部署(Azure Kubernetes Service) - -对于大规模部署或已使用 Kubernetes 的团队,AKS 提供更好的可扩展性和控制能力。 - -### AKS 架构 - -```mermaid -graph TB - subgraph AKS["Azure Kubernetes Service"] - Ingress["Ingress Controller
(AGIC 或 NGINX)"] - Pods["Teable Pods
(水平扩展)"] - end - - DB["Azure Database for
PostgreSQL Flexible Server"] - Redis["Azure Cache for Redis"] - S3["S3-compatible 存储
(MinIO 或 AWS S3)"] - - Ingress --> Pods - Pods --> DB - Pods --> Redis - Pods --> S3 - - style AKS fill:#326ce5,stroke:#1a4ba0,color:#fff - style DB fill:#0078d4,stroke:#005a9e,color:#fff - style Redis fill:#0078d4,stroke:#005a9e,color:#fff - style S3 fill:#ff9900,stroke:#cc7a00,color:#fff -``` - -### 步骤 1:创建 AKS 集群 - -```bash -# 创建 AKS 集群 -az aks create \ - --resource-group teable-rg \ - --name teable-aks \ - --location eastus \ - --node-count 2 \ - --node-vm-size Standard_D4s_v3 \ - --enable-managed-identity \ - --generate-ssh-keys - -# 获取凭据 -az aks get-credentials \ - --resource-group teable-rg \ - --name teable-aks -``` - -### 步骤 2:创建 Azure 托管服务 - -使用与 [步骤 1.2](#12-创建-postgresql-flexible-server) 和 [步骤 1.3](#13-创建-azure-cache-for-redis) 相同的命令创建 PostgreSQL 和 Redis。 - - -对于 AKS,建议启用 VNet 集成以保护连接: - -```bash -# 获取 AKS VNet ID -AKS_VNET=$(az aks show \ - --resource-group teable-rg \ - --name teable-aks \ - --query "networkProfile.networkPlugin" -o tsv) - -# 配置 PostgreSQL 接受来自 AKS VNet 的连接 -az postgres flexible-server vnet-rule create \ - --resource-group teable-rg \ - --server-name teable-db \ - --name aks-access \ - --vnet-name \ - --subnet -``` - - -### 步骤 3:在 AKS 中部署 MinIO(可选) - -如果希望存储保持在 Azure 内部,可以在 AKS 中部署 MinIO: - -```yaml -# minio-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: minio -spec: - replicas: 1 - selector: - matchLabels: - app: minio - template: - metadata: - labels: - app: minio - spec: - containers: - - name: minio - image: minio/minio:latest - args: - - server - - /data - - --console-address - - ":9001" - env: - - name: MINIO_ROOT_USER - value: "minioadmin" - - name: MINIO_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: minio-secrets - key: password - ports: - - containerPort: 9000 - - containerPort: 9001 - volumeMounts: - - name: data - mountPath: /data - volumes: - - name: data - persistentVolumeClaim: - claimName: minio-pvc ---- -apiVersion: v1 -kind: Service -metadata: - name: minio -spec: - ports: - - name: api - port: 9000 - targetPort: 9000 - - name: console - port: 9001 - targetPort: 9001 - selector: - app: minio ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: minio-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 50Gi - storageClassName: managed-premium -``` - - -对于生产环境的 MinIO 部署,建议使用 [MinIO Operator](https://min.io/docs/minio/kubernetes/upstream/) 实现分布式模式与数据冗余。 - - -### 步骤 4:创建 Kubernetes 配置 - -创建包含 Azure 特定设置的 ConfigMap: - -```yaml -# teable-config.yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: teable-config -data: - PUBLIC_ORIGIN: "https://teable.yourcompany.com" - - # 存储(AKS 内的 MinIO) - BACKEND_STORAGE_PROVIDER: "minio" - BACKEND_STORAGE_MINIO_ENDPOINT: "minio.yourcompany.com" - STORAGE_PREFIX: "https://minio.yourcompany.com" - BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT: "minio.default.svc.cluster.local" - BACKEND_STORAGE_MINIO_PORT: "443" - BACKEND_STORAGE_MINIO_INTERNAL_PORT: "9000" - BACKEND_STORAGE_MINIO_USE_SSL: "true" - - # 缓存 - BACKEND_CACHE_PROVIDER: "redis" - - # 其他 - NEXT_ENV_IMAGES_ALL_REMOTE: "true" - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" -``` - -创建包含 Azure 服务凭据的 Secrets: - -```yaml -# teable-secrets.yaml -apiVersion: v1 -kind: Secret -metadata: - name: teable-secrets -type: Opaque -stringData: - # PostgreSQL (Azure Flexible Server) - PRISMA_DATABASE_URL: "postgresql://teableadmin:<密码>@teable-db.postgres.database.azure.com:5432/postgres?sslmode=require" - - # 应用密钥 - SECRET_KEY: "<你的密钥>" - - # MinIO 凭据 - BACKEND_STORAGE_PUBLIC_BUCKET: "public" - BACKEND_STORAGE_PRIVATE_BUCKET: "private" - BACKEND_STORAGE_MINIO_ACCESS_KEY: "minioadmin" - BACKEND_STORAGE_MINIO_SECRET_KEY: "" - - # Redis (Azure Cache for Redis) - BACKEND_CACHE_REDIS_URI: "rediss://:@teable-cache.redis.cache.windows.net:6380/0" -``` - -### 步骤 5:部署 Teable - -```yaml -# teable-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: teable -spec: - replicas: 2 # 按需扩展 - selector: - matchLabels: - app: teable - template: - metadata: - labels: - app: teable - spec: - initContainers: - - name: db-migrate - image: ghcr.io/teableio/teable:latest - args: - - migrate-only - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - limits: - cpu: 1000m - memory: 1024Mi - containers: - - name: teable - image: ghcr.io/teableio/teable:latest - args: - - skip-migrate - ports: - - containerPort: 3000 - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - requests: - cpu: 500m - memory: 1Gi - limits: - cpu: 2000m - memory: 4Gi - livenessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 30 - periodSeconds: 30 - readinessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 15 - periodSeconds: 10 ---- -apiVersion: v1 -kind: Service -metadata: - name: teable -spec: - ports: - - port: 3000 - targetPort: 3000 - selector: - app: teable -``` - -### 步骤 6:配置 Ingress - -**方案 A:使用 NGINX Ingress Controller** - -```bash -# 安装 NGINX Ingress -helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx -helm install ingress-nginx ingress-nginx/ingress-nginx \ - --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz -``` - -```yaml -# teable-ingress.yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: teable - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/proxy-body-size: "100m" - cert-manager.io/cluster-issuer: letsencrypt-prod -spec: - tls: - - hosts: - - teable.yourcompany.com - secretName: teable-tls - rules: - - host: teable.yourcompany.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: teable - port: - number: 3000 -``` - -**方案 B:使用 Azure Application Gateway Ingress Controller (AGIC)** - -```bash -# 启用 AGIC 插件 -az aks enable-addons \ - --resource-group teable-rg \ - --name teable-aks \ - --addons ingress-appgw \ - --appgw-name teable-appgw \ - --appgw-subnet-cidr "10.225.0.0/16" -``` - -```yaml -# teable-ingress-agic.yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: teable - annotations: - kubernetes.io/ingress.class: azure/application-gateway - appgw.ingress.kubernetes.io/backend-path-prefix: "/" - appgw.ingress.kubernetes.io/ssl-redirect: "true" -spec: - tls: - - hosts: - - teable.yourcompany.com - secretName: teable-tls - rules: - - host: teable.yourcompany.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: teable - port: - number: 3000 -``` - -### 步骤 7:应用所有配置 - -```bash -# 应用配置 -kubectl apply -f teable-config.yaml -kubectl apply -f teable-secrets.yaml -kubectl apply -f minio-deployment.yaml # 如果在 AKS 中使用 MinIO -kubectl apply -f teable-deployment.yaml -kubectl apply -f teable-ingress.yaml - -# 验证部署 -kubectl get pods -l app=teable -kubectl logs -l app=teable -``` - -### AKS 生产环境建议 - -1. **自动扩缩容**: -```bash -# 启用 Horizontal Pod Autoscaler -kubectl autoscale deployment teable --cpu-percent=70 --min=2 --max=10 - -# 启用集群自动扩缩容 -az aks update \ - --resource-group teable-rg \ - --name teable-aks \ - --enable-cluster-autoscaler \ - --min-count 2 \ - --max-count 5 -``` - -2. **使用 Azure Key Vault** 替代 Kubernetes secrets 存储敏感信息: -```bash -# 启用 Key Vault secrets provider -az aks enable-addons \ - --resource-group teable-rg \ - --name teable-aks \ - --addons azure-keyvault-secrets-provider -``` - -3. **启用 Azure Monitor**: -```bash -az aks enable-addons \ - --resource-group teable-rg \ - --name teable-aks \ - --addons monitoring -``` - -4. **使用 Azure Private Link** 连接 PostgreSQL 和 Redis,保持流量在 Azure 网络内。 - ---- - -## 相关文档 - -- [私有化部署概览](/zh/deploy/production-overview) — 架构、配置与扩展 -- [环境变量参考](/zh/deploy/env) -- [对象存储(S3-compatible)](/zh/deploy/storage) -- [全功能平台](/zh/deploy/agent-runtime)(AI 能力与 App Builder) -- [AWS 部署](/zh/deploy/aws)(如使用跨云存储需参考 S3 设置) diff --git a/zh/deploy/database-connection.mdx b/zh/deploy/database-connection.mdx deleted file mode 100644 index 8c50caf4..00000000 --- a/zh/deploy/database-connection.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: "启用外部数据库连接" -description: "在应用中自动创建数据库角色允许外部应用对表格数据的安全访问功能" ---- - - -**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 -[选择部署方式](/zh/deploy/choose)开始。 - - - -外部数据库连接概览 - -要启用此功能,需要额外配置 `PUBLIC_DATABASE_PROXY` 环境变量 - -要将该参数配置为外部可访问的数据库 ip 或域名 + 端口号。如果你使用前面的 docker-compose 部署,则外部访问端口为 42345。其他方式请根据具体配置来进行 - -``` -PUBLIC_DATABASE_PROXY=db-proxy.example.com:port -``` - -## 更多环境变量配置 - -[环境变量](/zh/deploy/env) diff --git a/zh/deploy/env.mdx b/zh/deploy/env.mdx index d5d42b1c..6b2b1576 100644 --- a/zh/deploy/env.mdx +++ b/zh/deploy/env.mdx @@ -130,6 +130,7 @@ Infra Service、Git 仓库服务等)由 | TELEMETRY_REPORT_DISABLED | 禁用自托管遥测上报,包括联网实例的许可合规上报 | false | - | true | | **数据库配置** | | | | | | PRISMA_DATABASE_URL | 数据库连接 URL,必须配置 | - | 是 | postgresql://teable:teable@127.0.0.1:5432/teable | +| PUBLIC_DATABASE_PROXY | 对外可达的数据库地址(host:port),Base「数据库连接」面板生成的连接凭据会使用该地址;配置后即可从外部进行只读 SQL 访问 | - | - | db.example.com:42345 | | PRISMA_TRANSACTION_TIMEOUT | 事务运行超时时间(毫秒),对于长时间运行的事务(如包含多个外键的批量更新)可以增加此值 | 5000 | - | 60000 | | PRISMA_TRANSACTION_MAX_WAIT | 从连接池获取事务的最大等待时间(毫秒) | 2000 | - | 5000 | | BIG_TRANSACTION_TIMEOUT | 大型内部事务的超时时间(毫秒),例如导出大型数据库 | 600000 | - | 1200000 | diff --git a/zh/deploy/gcp.mdx b/zh/deploy/gcp.mdx deleted file mode 100644 index d1e12c5f..00000000 --- a/zh/deploy/gcp.mdx +++ /dev/null @@ -1,286 +0,0 @@ ---- -title: "在 GCP 上部署" -description: "分步指引:在 Google Cloud Platform 上使用 Cloud Run/GKE、Cloud SQL、Memorystore 与 S3-compatible 存储部署 Teable" ---- - - -**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 -[选择部署方式](/zh/deploy/choose)开始。 - - - - -**推荐场景**:50+ 用户的生产环境 - - -## 架构概览 - -```mermaid -graph TB - CloudRun["Cloud Run / GKE
Teable 应用
(自动扩展)"] - - CloudSQL["Cloud SQL
PostgreSQL"] - Memorystore["Memorystore for Redis
(队列 & 缓存)"] - S3["S3-compatible 存储
(AWS S3 或 MinIO)
Public + Private 双桶"] - - CloudRun --> CloudSQL - CloudRun --> Memorystore - CloudRun --> S3 - - style CloudRun fill:#4285f4,stroke:#2b5fab,color:#fff - style CloudSQL fill:#4285f4,stroke:#2b5fab,color:#fff - style Memorystore fill:#ea4335,stroke:#b8321e,color:#fff - style S3 fill:#ff9900,stroke:#cc7a00,color:#fff -``` - - -**重要**:Teable 需要 **S3-compatible 存储**。你可以选择: -- **AWS S3**(跨云方案) -- **MinIO**(在 GCE/GKE 自建) -- 其他 S3-compatible 服务 - -Google Cloud Storage (GCS) **不直接支持**,因为它使用不同的 API。 - - -## 前置要求 - -- 已安装并认证 `gcloud` CLI -- GCP 项目已启用计费 -- 具备 **S3-compatible 存储**访问权(例如 AWS S3 账号或 MinIO 部署) - ---- - -## 步骤 1:创建 GCP 资源 - -### 1.1 设置项目和区域 - -```bash -export PROJECT_ID=your-project-id -export REGION=us-central1 - -gcloud config set project $PROJECT_ID -``` - -### 1.2 创建 Cloud SQL (PostgreSQL) - -```bash -gcloud sql instances create teable-db \ - --database-version=POSTGRES_16 \ - --tier=db-custom-2-7680 \ - --region=$REGION \ - --root-password=<你的强密码> \ - --storage-type=SSD \ - --storage-size=100GB -``` - - -获取连接名称: -```bash -gcloud sql instances describe teable-db \ - --format='value(connectionName)' -``` -结果:`your-project:region:teable-db` - - -创建数据库: -```bash -gcloud sql databases create teable --instance=teable-db -``` - -### 1.3 创建 Memorystore (Redis) - -```bash -gcloud redis instances create teable-cache \ - --size=1 \ - --region=$REGION \ - --redis-version=redis_7_0 -``` - - -获取 Redis 主机地址: -```bash -gcloud redis instances describe teable-cache \ - --region=$REGION \ - --format='value(host)' -``` - - ---- - -## 步骤 2:设置 S3-compatible 存储 - -由于 Google Cloud Storage 不兼容 S3,请选择以下方案之一: - -### 方案 A:使用 AWS S3(跨云) - -1. **在 AWS 上创建 S3 bucket**(参见 [AWS 部署指引](/zh/deploy/aws) 步骤 1.3-1.4) -2. **配置 public bucket**(参见 [对象存储指引](/zh/deploy/storage)) - -环境变量: -```bash -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY= -BACKEND_STORAGE_S3_SECRET_KEY= -BACKEND_STORAGE_PUBLIC_BUCKET=teable-public-<后缀> -BACKEND_STORAGE_PRIVATE_BUCKET=teable-private-<后缀> -STORAGE_PREFIX=https://teable-public-<后缀>.s3.us-west-2.amazonaws.com -``` - -### 方案 B:在 GCP 上部署 MinIO - -在 GCE 实例或 GKE 集群上部署 MinIO 提供 S3-compatible 存储。 - -**推荐镜像**:`minio/minio:RELEASE.2025-04-22T22-12-26Z` - -**快速设置(GCE VM)**: - -```bash -# 创建 VM -gcloud compute instances create minio-server \ - --machine-type=e2-medium \ - --zone=us-central1-a \ - --image-family=ubuntu-2204-lts \ - --image-project=ubuntu-os-cloud \ - --boot-disk-size=100GB - -# SSH 登录并安装 MinIO -gcloud compute ssh minio-server --zone=us-central1-a - -# 在 VM 上: -wget https://dl.min.io/server/minio/release/linux-amd64/minio -chmod +x minio -sudo mv minio /usr/local/bin/ - -export MINIO_ROOT_USER=admin -export MINIO_ROOT_PASSWORD=<强密码> -minio server /data --console-address ":9001" -``` - -创建 bucket 并配置访问(详见 Azure 指引方案 B)。 - ---- - -## 步骤 3:准备环境变量 - -创建 `.env` 文件: - -```bash -# Core -PUBLIC_ORIGIN=https://teable.yourcompany.com -SECRET_KEY=<生成32位随机字符串> - -# Database (Cloud SQL) -PRISMA_DATABASE_URL=postgresql://postgres:<密码>@/teable?host=/cloudsql/ - -# Redis (Memorystore) -BACKEND_CACHE_PROVIDER=redis -BACKEND_CACHE_REDIS_URI=redis://:6379/0 -BACKEND_PERFORMANCE_CACHE=redis://:6379/0 - -# Storage (S3-compatible) -# 方案 A (AWS S3): -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY= -BACKEND_STORAGE_S3_SECRET_KEY= -BACKEND_STORAGE_PUBLIC_BUCKET=teable-public-<后缀> -BACKEND_STORAGE_PRIVATE_BUCKET=teable-private-<后缀> -STORAGE_PREFIX=https://teable-public-<后缀>.s3.us-west-2.amazonaws.com -``` - ---- - -## 步骤 4:部署到 Cloud Run - -### 4.1 构建并推送容器(或使用预构建镜像) - -```bash -# 方案 1:使用预构建镜像 -export IMAGE=ghcr.io/teableio/teable:latest - -# 方案 2:构建并推送到 GCR -# gcloud builds submit --tag gcr.io/$PROJECT_ID/teable -# export IMAGE=gcr.io/$PROJECT_ID/teable -``` - -### 4.2 部署到 Cloud Run - -```bash -gcloud run deploy teable \ - --image=$IMAGE \ - --platform=managed \ - --region=$REGION \ - --allow-unauthenticated \ - --port=3000 \ - --set-env-vars="PUBLIC_ORIGIN=https://teable.yourcompany.com" \ - --set-env-vars="SECRET_KEY=<你的secret>" \ - --set-env-vars="PRISMA_DATABASE_URL=postgresql://..." \ - --set-env-vars="BACKEND_CACHE_PROVIDER=redis" \ - --set-env-vars="BACKEND_CACHE_REDIS_URI=redis://..." \ - --set-env-vars="BACKEND_STORAGE_PROVIDER=s3" \ - --set-env-vars="BACKEND_STORAGE_S3_REGION=us-west-2" \ - --set-env-vars="BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com" \ - --set-env-vars="BACKEND_STORAGE_S3_ACCESS_KEY=***" \ - --set-env-vars="BACKEND_STORAGE_S3_SECRET_KEY=***" \ - --set-env-vars="BACKEND_STORAGE_PUBLIC_BUCKET=teable-public-xxx" \ - --set-env-vars="BACKEND_STORAGE_PRIVATE_BUCKET=teable-private-xxx" \ - --set-env-vars="STORAGE_PREFIX=https://teable-public-xxx.s3.us-west-2.amazonaws.com" \ - --add-cloudsql-instances= \ - --vpc-connector= -``` - - -访问 Cloud SQL 使用 `--add-cloudsql-instances`。 -访问 Memorystore 需先创建 VPC connector。 - - ---- - -## 步骤 5:验证部署 - -1. **获取服务 URL**: - -```bash -gcloud run services describe teable \ - --region=$REGION \ - --format='value(status.url)' -``` - -2. **测试健康检查**: - -```bash -curl https:///health -``` - -预期:`{"status":"ok"}` - -3. **查看日志**: - -```bash -gcloud run services logs read teable --region=$REGION -``` - ---- - -## 生产环境最佳实践 - - -详细的生产环境建议(包括配置、高可用、扩展策略)请参考 [私有化部署概览](/zh/deploy/production-overview)。 - - -**GCP 特定建议**: -- 启用 Cloud Run 最小实例数(2+)实现高可用 -- 使用 Secret Manager 存储敏感值 -- 启用 Cloud Monitoring 并设置告警 -- 将自定义域名映射到 Cloud Run 服务 - ---- - -## 相关文档 - -- [私有化部署概览](/zh/deploy/production-overview) — 架构、配置与扩展 -- [环境变量参考](/zh/deploy/env) -- [对象存储(S3-compatible)](/zh/deploy/storage) diff --git a/zh/deploy/k8s.mdx b/zh/deploy/k8s.mdx deleted file mode 100644 index b62a45b9..00000000 --- a/zh/deploy/k8s.mdx +++ /dev/null @@ -1,435 +0,0 @@ ---- -title: "K8s 部署" ---- - - -**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 -[选择部署方式](/zh/deploy/choose)开始。 Kubernetes 部署现统一使用全功能 Helm chart —— 见 [Agent Runtime 架构](/zh/deploy/agent-runtime)。 - - - - -**推荐场景**:50+ 用户的生产环境 - - - -配置建议(CPU、内存、副本数)请参考 [私有化部署概览 → 配置建议](/zh/deploy/production-overview#配置建议)。 - - -## 前置条件 -### Teable 镜像 -- 应用镜像: `registry.cn-shenzhen.aliyuncs.com/teable/teable:latest` - -### 运行环境 -- 运行中的 Kubernetes 集群 - -### 已配置的外部服务 -#### 数据库服务 -- PostgreSQL 数据库: postgres:15.4(版本号大于 12) - - PostgreSQL 镜像: `registry.cn-shenzhen.aliyuncs.com/teable/postgres:15.4` - -#### 缓存服务 -- Redis 缓存服务: redis:7.2.4(版本号大于 5) - - Redis 镜像: `registry.cn-shenzhen.aliyuncs.com/teable/redis:7.2.4` - -#### 对象存储服务 -- MinIO 对象存储服务: minio:RELEASE.2024-10-13T13-34-11Z-cpuv1 - - MinIO 镜像: `registry.cn-shenzhen.aliyuncs.com/teable/minio:RELEASE.2024-10-13T13-34-11Z-cpuv1` - - 注意: 可以直接使用最新版本,或者使用云存储,配置参考,下方示例使用 minio 来做例子。 - -### 必要工具 -- kubectl 命令行工具 - -## 依赖组件配置 -### 文件存储 MinIO -文件存储服务需要支持外网访问(即终端用户可直接访问到) - -文件存储需要预先创建好两个 bucket: - -| Bucket | 环境变量 | 权限 | -|--------|---------|------| -| 公共桶 | `BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub` | **公共可读(必须开启匿名访问)** | -| 私有桶 | `BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt` | 私有(默认) | - - -公共桶**必须**开启匿名读取权限。否则用户将无法查看共享图片、头像等公开文件。 - - -#### 使用 MinIO Client (mc) 创建 Bucket - -在部署 Teable 之前,请先创建所需的 bucket: - -```sh -# 设置 MinIO 客户端别名 -mc alias set teable-minio https://minio.example.com YOUR_ACCESS_KEY YOUR_SECRET_KEY - -# 创建公共桶并设置匿名读取权限 -mc mb --ignore-existing teable-minio/teable-pub -mc anonymous set public teable-minio/teable-pub - -# 创建私有桶 -mc mb --ignore-existing teable-minio/teable-pvt - -# 验证 bucket 是否创建成功 -mc ls teable-minio -# 预期输出: -# [2024-01-01 00:00:00 UTC] 0B teable-pub/ -# [2024-01-01 00:00:00 UTC] 0B teable-pvt/ -``` - -#### Teable MinIO 环境变量总览 - -```sh -# 固定值 -BACKEND_STORAGE_PROVIDER=minio -# 公共桶 -BACKEND_STORAGE_PUBLIC_BUCKET=teable-pub -# 私有桶 -BACKEND_STORAGE_PRIVATE_BUCKET=teable-pvt -# 外网端点,重要!需要终端用户可以直接访问到 -BACKEND_STORAGE_MINIO_ENDPOINT=minio.example.com -# 与前面的值保持一样,但是要加个协议名称 -STORAGE_PREFIX=https://minio.example.com -# 内网端点(Kubernetes DNS 格式:..svc.cluster.local) -# ⚠️ 请将 替换为实际的命名空间,例如:minio.teable.svc.cluster.local -BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT=minio.teable.svc.cluster.local -# 外网端口,一般为 443 或者 9000 -BACKEND_STORAGE_MINIO_PORT=443 -# 内网端口,一般为 80 或者 9000 -BACKEND_STORAGE_MINIO_INTERNAL_PORT=80 -# 是否启用 HTTPS, 注意如果 Teable 启用了 https 则 minio 也必须启用 https 否则有跨域问题 -BACKEND_STORAGE_MINIO_USE_SSL="true" -# 管理员账号 -BACKEND_STORAGE_MINIO_ACCESS_KEY=root -# 管理员密码 -BACKEND_STORAGE_MINIO_SECRET_KEY=rootPassword -``` - -### 数据库 -需要创建一个具有管理员权限的数据库账户,以及一个数据库,并且设置密码。 -对应环境变量示例: - - 数据库名称: teable - - 密码:your-password - - 账号: postgres - - 端口: 5432 -```sh -PRISMA_DATABASE_URL="postgresql://postgres:your-password@your-postgres-host:5432/teable" -``` - -### 缓存 Redis -Teable 配置 redis 缓存只需要提供内网连接地址即可。(注意 Redis 不仅管理缓存还管理队列,不可或缺。数据需要定时备份) -对应环境变量示例 -```sh -BACKEND_CACHE_REDIS_URI="redis://username:password@your-redis-host:6379/0" -``` - -## 创建配置文件 - -```yaml teable-config.yaml(非敏感配置) -apiVersion: v1 -kind: ConfigMap -metadata: - name: teable-config - namespace: teable # 请替换为你的命名空间 -data: - # 应用基础配置,对外访问的域名 - PUBLIC_ORIGIN: "https://your-domain.com" - - # 存储配置 - BACKEND_STORAGE_PROVIDER: "minio" - # Bucket 名称(需要在部署前于 MinIO 中创建) - BACKEND_STORAGE_PUBLIC_BUCKET: "teable-pub" - BACKEND_STORAGE_PRIVATE_BUCKET: "teable-pvt" - # 外网端点,重要!需要终端用户可以直接访问到 - BACKEND_STORAGE_MINIO_ENDPOINT: "minio.example.com" - # 与前面的值保持一样,但是要加个协议名称 - STORAGE_PREFIX: "https://minio.example.com" - # 内网端点(Kubernetes DNS 格式:..svc.cluster.local) - # ⚠️ 请将 'teable' 替换为实际的命名空间 - BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT: "minio.teable.svc.cluster.local" - # 外网端口,一般为 443 或者 9000 - BACKEND_STORAGE_MINIO_PORT: "443" - # 内网端口,一般为 80 或者 9000 - BACKEND_STORAGE_MINIO_INTERNAL_PORT: "80" - # 是否启用 HTTPS, 注意如果 Teable 启用了 https 则 minio 也必须启用 https 否则有跨域问题 - BACKEND_STORAGE_MINIO_USE_SSL: "true" - - # 缓存配置, 固定值 - BACKEND_CACHE_PROVIDER: "redis" - - # 其他配置,固定值 - NEXT_ENV_IMAGES_ALL_REMOTE: "true" - PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1" - # 使用自签发证书需要保留这个 - NODE_TLS_REJECT_UNAUTHORIZED: '0' -``` - -```yaml teable-secrets.yaml(敏感信息) -apiVersion: v1 -kind: Secret -metadata: - name: teable-secrets - namespace: teable # 请替换为你的命名空间 -type: Opaque -stringData: - # 数据库敏感信息 - PRISMA_DATABASE_URL: "postgresql://postgres:your-password@your-postgres-host:5432/teable" - - # 应用密钥 - BACKEND_JWT_SECRET: "your-jwt-secret" - BACKEND_SESSION_SECRET: "your-session-secret" - - # MinIO 认证信息 - BACKEND_STORAGE_MINIO_ACCESS_KEY: "your-minio-access-key" - BACKEND_STORAGE_MINIO_SECRET_KEY: "your-minio-secret-key" - - # Redis 认证信息 - BACKEND_CACHE_REDIS_URI: "redis://username:password@your-redis-host:6379/0" -``` - - -```yaml teable-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: teable - namespace: teable # 请替换为你的命名空间 -spec: - replicas: 1 # 根据需要配置 - selector: - matchLabels: - app: teable - template: - metadata: - labels: - app: teable - spec: - # Add initContainers for database migration - initContainers: - - name: db-migrate - image: registry.cn-shenzhen.aliyuncs.com/teable/teable:latest - args: - - migrate-only - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - requests: - cpu: 100m - memory: 102Mi - limits: - cpu: 1000m - memory: 1024Mi - containers: - - name: teable - image: registry.cn-shenzhen.aliyuncs.com/teable/teable:latest - args: - - skip-migrate - ports: - - containerPort: 3000 - envFrom: - - configMapRef: - name: teable-config - - secretRef: - name: teable-secrets - resources: - requests: - cpu: 200m - memory: 400Mi - limits: - cpu: 2000m - memory: 4096Mi - startupProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 10 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 30 - successThreshold: 1 - livenessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 30 - periodSeconds: 30 - timeoutSeconds: 5 - failureThreshold: 3 - successThreshold: 1 - readinessProbe: - httpGet: - path: /health - port: 3000 - initialDelaySeconds: 15 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 3 - successThreshold: 1 -``` - -```yaml teable-service.yaml -apiVersion: v1 -kind: Service -metadata: - name: teable - namespace: teable # 请替换为你的命名空间 -spec: - ports: - - port: 3000 - targetPort: 3000 - selector: - app: teable -``` - -```yaml teable-ingress.yaml (可选) -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: teable - namespace: teable # 请替换为你的命名空间 - # 以 nginx 做示例,如果使用其他的 ingress class 请替换 - annotations: - nginx.ingress.kubernetes.io/proxy-body-size: "100m" - nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" - nginx.ingress.kubernetes.io/proxy-send-timeout: "600" - nginx.ingress.kubernetes.io/proxy-read-timeout: "600" - nginx.ingress.kubernetes.io/proxy-request-buffering: "off" -spec: - ingressClassName: nginx # 如果使用其他 ingress class 请修改 - rules: - - host: your-domain.com # 请替换为你的域名 - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: teable - port: - number: 3000 - tls: - - hosts: - - your-domain.com # 请替换为你的域名 - secretName: your-tls-secret -``` - - -**请勿**添加 `nginx.ingress.kubernetes.io/rewrite-target` 注解,这会破坏 Teable 的内部路由。 - - - -## 部署步骤 - - -在部署 Teable 之前,请确保: -1. MinIO bucket(`teable-pub` 和 `teable-pvt`)已创建 -2. 公共桶已开启匿名读取权限 -3. PostgreSQL 数据库 `teable` 已存在 -4. Redis 服务可访问 - - -1. 创建命名空间(如果不存在): -```sh -kubectl create namespace teable -``` - -2. 创建配置和密钥: -```sh -kubectl apply -f teable-config.yaml -kubectl apply -f teable-secrets.yaml -``` - -3. 部署应用: -```sh -kubectl apply -f teable-deployment.yaml -kubectl apply -f teable-service.yaml -kubectl apply -f teable-ingress.yaml # 可选,如果使用 Ingress -``` - -4. 验证部署: -```sh -# 检查 Pod 状态 -kubectl get pods -n teable -l app=teable - -# 等待 Pod 就绪 -kubectl rollout status deployment/teable -n teable - -# 查看应用日志 -kubectl logs -n teable -l app=teable -``` - -### 配置注意事项 - -1. 敏感信息管理 - - 所有密码、密钥类信息应通过 Secrets 管理 - -2. 数据库配置 - - 确保数据库已创建 teable 数据库 - - 数据库用户需要具备适当权限 - - 建议使用连接池管理数据库连接 - -3. MinIO 配置 - - 确保存储桶已创建且权限正确, 一个公共桶,一个私有桶 - - 公共桶需要完全公开读 - - 内外部访问地址配置正确 - -4. Redis 配置 - - 建议启用 Redis 持久化 - - 配置适当的内存限制 - - 考虑使用 Redis 集群提高可用性 - -5. 安全建议 - - 使用强密码和密钥 - - 启用 TLS/SSL 加密 - - 定期更新证书 - - 限制网络访问范围 - -6. 资源配置 - - 根据实际负载调整资源限制 - - 监控资源使用情况 - - 配置适当的健康检查参数 - -### 故障排查 - -#### 常见问题 - -| 错误信息 | 原因 | 解决方案 | -|---------|------|---------| -| `ECONNREFUSED` 连接 MinIO 失败 | MinIO 内网端点配置错误或不可达 | 检查 `BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT` 格式是否正确:`..svc.cluster.local` | -| `Bucket not found` | MinIO bucket 未创建 | 在部署前使用 `mc mb` 命令创建 bucket | -| `Access Denied` 文件上传失败 | MinIO 凭证错误 | 检查 `BACKEND_STORAGE_MINIO_ACCESS_KEY` 和 `SECRET_KEY` | -| 访问页面显示 `Page not found` | Ingress 配置了 `rewrite-target` | 移除 Ingress 中的 `rewrite-target` 注解 | -| Pod 处于 `CrashLoopBackOff` 状态 | 后端启动失败 | 检查日志,确认 MinIO、数据库、Redis 连接是否正常 | - -#### 诊断命令 - -1. 检查 Pod 状态: -```sh -kubectl describe pod -n teable -l app=teable -``` - -2. 查看应用日志: -```sh -kubectl logs -n teable -l app=teable -kubectl logs -n teable -l app=teable --previous # 如果 Pod 崩溃 -``` - -3. 验证配置: -```sh -kubectl describe configmap teable-config -n teable -kubectl describe secret teable-secrets -n teable -``` - -4. 检查网络连接: -```sh -kubectl exec -it -n teable -- curl -v localhost:3000/health -``` - -5. 测试 MinIO 集群内部连接: -```sh -kubectl run test-minio --rm -it --image=curlimages/curl --restart=Never -n teable -- \ - curl -v http://minio.teable.svc.cluster.local/minio/health/live -``` diff --git a/zh/deploy/one-key.mdx b/zh/deploy/one-key.mdx deleted file mode 100644 index 16cb191c..00000000 --- a/zh/deploy/one-key.mdx +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: "一键云部署" -description: "通过云服务商集成的配置模板,你可以一键部署完整的,可伸缩的 Teable 服务" ---- - - -**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 -[选择部署方式](/zh/deploy/choose)开始。 下方模板当前不可用。 - - -| 优点 | 缺点 | -| ----------- | ------ | -| 无需任何配置,一键启动 | 依赖云服务商 | -| 可以弹性伸缩 | | -| 运维简单 | | - -**系统要求**: - -* 应用节点最低 1c/2g -* 数据库最低 1c/2g - -#### Sealos - -有免费额度,对国内支持友好,按量计费 - -[**一键部署**](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dteable) - -### 订阅与激活(付费功能) - -如果你是**自托管部署**并需要付费功能,请在部署完成后进行订阅并激活许可证: - -1. 在你部署好的 Teable 实例中进入管理员后台,复制 **Instance ID(实例 ID)** -2. 打开自托管订阅页面完成订阅:`https://app.teable.ai/public/pricing?host=self-hosted` -3. 订阅成功后会获得 **License Key(许可证密钥)**,回到实例中完成激活 - -详情请见:[订阅和激活许可证](/zh/deploy/activate) - -### 下一步 - -[配置邮件服务](/zh/deploy/email) - -[配置外部数据库连接](/zh/deploy/database-connection) diff --git a/zh/deploy/production-overview.mdx b/zh/deploy/production-overview.mdx deleted file mode 100644 index 4eff4fb3..00000000 --- a/zh/deploy/production-overview.mdx +++ /dev/null @@ -1,183 +0,0 @@ ---- -title: "概览" -description: "Teable 生产环境部署的架构、配置建议和部署路径选择" ---- - - -**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 -[选择部署方式](/zh/deploy/choose)开始。 - - - - -**推荐场景**:50+ 用户的生产环境 - - - -本页介绍**基础版** Teable 应用的规模化部署。如需 AI 能力或 App Builder, -请改用[全功能平台](/zh/deploy/agent-runtime) —— 它包含应用与 AI 运行平面。 - - -## 架构概览 - -Teable 采用**无状态应用层**配合**外部状态服务**的设计,支持水平扩展和高可用。 - -```mermaid -graph TB - subgraph "应用层 (无状态)" - App1["Teable Pod 1"] - App2["Teable Pod 2"] - AppN["Teable Pod N..."] - end - - subgraph "数据层 (有状态)" - DB["PostgreSQL
(主 + 从)"] - Redis1["Redis
队列与缓存"] - Redis2["Redis
性能缓存"] - S3["S3 / MinIO
对象存储"] - end - - App1 --> DB - App2 --> DB - AppN --> DB - App1 --> Redis1 - App1 --> Redis2 - App1 --> S3 - - style App1 fill:#0D9373,stroke:#0a7a5e,color:#fff - style App2 fill:#0D9373,stroke:#0a7a5e,color:#fff - style AppN fill:#0D9373,stroke:#0a7a5e,color:#fff - style DB fill:#336791,stroke:#264d73,color:#fff - style Redis1 fill:#dc382d,stroke:#a82a22,color:#fff - style Redis2 fill:#dc382d,stroke:#a82a22,color:#fff - style S3 fill:#ff9900,stroke:#cc7a00,color:#fff -``` - -### 组件说明 - -| 组件 | 用途 | 托管服务示例 | -|------|------|-------------| -| **Teable 应用** | 无状态 Web 应用 | K8s, AWS ECS, Azure App Service, GCP Cloud Run | -| **PostgreSQL** | 主数据存储 | AWS RDS, Azure PostgreSQL, Google Cloud SQL | -| **Redis (队列)** | 后台任务与缓存 | ElastiCache, Azure Cache, Memorystore | -| **Redis (性能)** | 读取性能缓存 | 可使用同一个或独立实例 | -| **对象存储** | 文件与附件 | AWS S3, MinIO, 任何 S3 兼容存储 | - - -**核心要点**:Teable 应用不持有任何持久化状态,可以通过增加 Pod 实现水平扩展,任何 Pod 都能处理任何请求。 - - ---- - -## 配置建议 - - -以下是 Teable 工程团队的**生产环境**配置建议。如需评估测试,请参考 [Docker 快速部署](/zh/deploy/docker)。 - - -### 应用层 (Teable Pods) - -| 配置项 | 建议值 | -|--------|--------| -| 每 Pod CPU | 2 vCPU | -| 每 Pod 内存 | 4 GB | -| 最小副本数 | 2 | -| 自动扩容触发 | CPU 使用率 50% | - - -使用 Kubernetes 的 Horizontal Pod Autoscaler (HPA),或 AWS ECS / Azure App Service / GCP Cloud Run 的等效自动扩缩容功能。 - - -### 数据库 (PostgreSQL) - -| 配置项 | 建议值 | -|--------|--------| -| CPU | 4 vCPU | -| 内存 | 16 GB | -| 拓扑 | 1 主 + 1 从 | -| 备份 | 每日自动备份 | -| 版本 | 15+ 推荐 | - - -务必配置从库以实现高可用。使用托管数据库服务可获得自动故障转移能力。 - - -### 缓存 (Redis) - -Teable 使用**两个独立的 Redis 实例**: - -| 用途 | 环境变量 | CPU | 内存 | -|------|----------|-----|------| -| 队列与缓存 | `BACKEND_CACHE_REDIS_URI` | 1 vCPU | 2 GB | -| 性能缓存 | `BACKEND_PERFORMANCE_CACHE` | 2 vCPU | 4 GB | - - -性能缓存 (`BACKEND_PERFORMANCE_CACHE`) 可选但**强烈建议**配置,对多人协作场景能显著提升读取性能。 - - -### 对象存储 - -- **类型**:S3 兼容存储(AWS S3, MinIO 等) -- **存储桶**:需要 2 个(公开 + 私有) -- **容量**:使用托管 S3 服务可自动扩展 - -详见 [对象存储配置指南](/zh/deploy/storage)。 - ---- - -## 扩容策略 - -| 组件 | 扩容方式 | 扩容时机 | -|------|----------|----------| -| Teable 应用 | **自动**(水平扩展) | CPU > 50%(增加 Pod) | -| PostgreSQL | **手动**(垂直扩展) | CPU > 70% 持续 | -| Redis | **手动**(垂直扩展) | 内存 > 80% 或 CPU > 60% | - - -**应用层**:无状态设计支持自动水平扩展。Kubernetes HPA 或云服务自动扩缩容会根据 CPU 指标自动处理。 - -**数据库和 Redis**:这些是有状态服务,垂直扩展(更大的实例)比水平扩展更简单安全。监控指标,在超过阈值时进行扩容。 - - ---- - -## 最小配置 vs 生产配置 - -| | 最小配置 (PoC) | 生产配置 | -|---|----------------|----------| -| Teable 应用 | 1 实例, 2c4g | 2+ Pod, 每个 2c4g, 自动扩缩容 | -| PostgreSQL | 2c4g, 单实例 | 4c16g, 1 主 + 1 从 | -| Redis | 1 实例, 1c2g | 2 实例(队列 + 性能) | -| 存储 | S3 兼容对象存储 | S3 兼容对象存储 | -| 可用性 | 无(单点故障) | 高可用冗余 | - - -"最小配置"仅供**评估测试**使用,没有冗余——服务器故障可能导致数据丢失。 - - ---- - -## 下一步:选择部署平台 - - - - 应用 + AI 运行平面,一个 Helm chart 装进任意 K8s 集群。 - - - ECS + RDS + ElastiCache + S3,推荐 50+ 用户使用。 - - - App Service + PostgreSQL + Redis + S3 兼容存储。 - - - Cloud Run + Cloud SQL + Memorystore + Cloud Storage。 - - - ---- - -## 相关文档 - -- [环境变量参考](/zh/deploy/env) — 所有配置选项 -- [对象存储配置](/zh/deploy/storage) — S3 存储桶配置 -- [许可证激活](/zh/deploy/activate) — 激活私有化部署许可证 diff --git a/zh/deploy/storage.mdx b/zh/deploy/storage.mdx deleted file mode 100644 index 471e9e3c..00000000 --- a/zh/deploy/storage.mdx +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: "对象存储(S3-compatible)" -description: "Teable 生产环境存储指引:public/private 双桶、权限与跨域(CORS)" ---- - - -**已弃用。**本页不再维护,已从文档目录中移除。当前的部署指引请从 -[选择部署方式](/zh/deploy/choose)开始。 对象存储现已随部署资产预配置;全功能平台会为你管理三个存储桶(公开、私有、构建产物)。 - - - - -生产环境请使用 **S3-compatible 对象存储**(如 S3、MinIO、兼容 S3 的 OSS/OBS/COS 等),并按 **public/private 双桶**进行配置。 - - -## 为什么要两个 Bucket? - -Teable 会将不同类型的文件放在不同 bucket: - -- **Public bucket**:头像、表单头图等需要公开访问的资源。 -- **Private bucket**:附件、应用文件、记录历史冷存储分片等需要权限保护的资源。 - -## 必需环境变量 - -最少需要配置: - -- **Provider** - - `BACKEND_STORAGE_PROVIDER`:`s3` 或 `minio` -- **Buckets** - - `BACKEND_STORAGE_PUBLIC_BUCKET` - - `BACKEND_STORAGE_PRIVATE_BUCKET` -- **公共访问前缀** - - `STORAGE_PREFIX`:建议设置为 **public bucket 的访问基址**(或其前面的 CDN 域名) - -### S3 Provider 示例 - -```bash -BACKEND_STORAGE_PROVIDER=s3 -BACKEND_STORAGE_S3_REGION=us-west-2 -BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com -BACKEND_STORAGE_S3_ACCESS_KEY=***REDACTED*** -BACKEND_STORAGE_S3_SECRET_KEY=***REDACTED*** - -BACKEND_STORAGE_PUBLIC_BUCKET=your-public-bucket -BACKEND_STORAGE_PRIVATE_BUCKET=your-private-bucket - -# public bucket 的访问基址(或 CDN) -STORAGE_PREFIX=https://your-public-bucket.s3.us-west-2.amazonaws.com -``` - -### MinIO Provider 示例 - -```bash -BACKEND_STORAGE_PROVIDER=minio -BACKEND_STORAGE_MINIO_ENDPOINT=minio.example.com -BACKEND_STORAGE_MINIO_PORT=443 -BACKEND_STORAGE_MINIO_USE_SSL=true -BACKEND_STORAGE_MINIO_ACCESS_KEY=***REDACTED*** -BACKEND_STORAGE_MINIO_SECRET_KEY=***REDACTED*** - -BACKEND_STORAGE_PUBLIC_BUCKET=public -BACKEND_STORAGE_PRIVATE_BUCKET=private -STORAGE_PREFIX=https://minio.example.com -``` - -## Public bucket 必做项 - -### 1)Public Read(公共读) - -**public bucket 必须允许公开读**对象。 - -以 AWS S3 为例,可配置 Bucket Policy: - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "PublicReadGetObject", - "Effect": "Allow", - "Principal": "*", - "Action": "s3:GetObject", - "Resource": "arn:aws:s3:::your-public-bucket/*" - } - ] -} -``` - - -AWS S3 可能需要调整 public bucket 的 **Block Public Access** 相关选项。请遵循你们公司的安全规范。 - - -### 2)CORS(允许任意跨域) - -为保证浏览器端访问与上传,public bucket 需要配置 CORS,允许跨域请求。 - -AWS S3 示例: - -```json -[ - { - "AllowedHeaders": ["*"], - "AllowedMethods": ["GET", "HEAD"], - "AllowedOrigins": ["*"], - "ExposeHeaders": ["ETag", "x-amz-request-id", "x-amz-id-2"], - "MaxAgeSeconds": 3000 - } -] -``` - -如果你们启用 **浏览器直传(presigned PUT)**,则可能还需要把 `PUT` 加到 `AllowedMethods`。 - -## Private bucket 提示 - -- **private bucket** 必须保持私有(禁止 public read)。 -- Teable 会根据功能,通过 **签名 URL** 或后端读取来访问 private 对象。 -- 记录历史冷存储会把压缩后的历史分片写入 private bucket,请让 bucket 备份和保留策略与记录历史要求保持一致。 -- 如需为 private bucket 使用独立域名/endpoint,可配置: - - `BACKEND_STORAGE_PRIVATE_BUCKET_ENDPOINT`(可选) - -## Azure / GCP 说明 - -Teable 对象存储使用 **S3 API**。Azure Blob Storage 与 Google Cloud Storage **并非 S3-native**。 - -- 在 **Azure / GCP** 上部署时,只要 Teable 运行环境能访问到某个 S3 endpoint(例如 AWS S3),同样可以直接使用 `BACKEND_STORAGE_PROVIDER=s3`。 -- 或选用 **S3-compatible** 的对象存储: - - 自建 MinIO(作为 S3 endpoint) - - 或其他你们选择的托管 S3-compatible 服务 From 5cb2501fa56473887e9846c6aea1184179d76e1f Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 20:07:46 +0800 Subject: [PATCH 06/12] =?UTF-8?q?docs(self-host):=20architecture-first=20n?= =?UTF-8?q?arrative=20=E2=80=94=20Teable=20is=20an=20AI=20product?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename Agent Runtime Architecture to Architecture (/deploy/architecture, old URL redirected) and fold the Teable app, PostgreSQL, Redis, and the three-bucket object storage into one integrated architecture page; the AI-less standalone becomes the secondary path. Hide Docker Deployment from the navigation (page stays linked from Choose Your Path), put the full-featured column before standalone in the comparison table, and add an agent-driven deployment guide (domain + DNS token + target access, with CN-localized providers on the zh page). Drop the env-page intro tip and the outdated 'early stage' caveat in app-builder. Co-Authored-By: Claude Fable 5 --- docs.json | 14 ++-- en/basic/admin-panel/ai-setting.mdx | 6 +- en/basic/admin-panel/sandbox-agent.mdx | 2 +- en/basic/ai/app-builder.mdx | 2 +- en/deploy/activate.mdx | 2 +- .../{agent-runtime.mdx => architecture.mdx} | 66 +++++++++++-------- en/deploy/choose.mdx | 36 ++++++++-- en/deploy/docker.mdx | 2 +- en/deploy/env.mdx | 6 -- en/deploy/upgrade.mdx | 2 +- zh/basic/admin-panel/ai-setting.mdx | 6 +- zh/basic/admin-panel/sandbox-agent.mdx | 2 +- zh/basic/ai/app-builder.mdx | 2 +- zh/deploy/activate.mdx | 2 +- .../{agent-runtime.mdx => architecture.mdx} | 55 +++++++++------- zh/deploy/choose.mdx | 32 +++++++-- zh/deploy/docker.mdx | 2 +- zh/deploy/env.mdx | 5 -- zh/deploy/upgrade.mdx | 2 +- 19 files changed, 154 insertions(+), 92 deletions(-) rename en/deploy/{agent-runtime.mdx => architecture.mdx} (65%) rename zh/deploy/{agent-runtime.mdx => architecture.mdx} (67%) diff --git a/docs.json b/docs.json index 8dc66c3f..c32828ba 100644 --- a/docs.json +++ b/docs.json @@ -99,6 +99,14 @@ { "source": "/zh/deploy/database-connection", "destination": "/zh/deploy/env" + }, + { + "source": "/en/deploy/agent-runtime", + "destination": "/en/deploy/architecture" + }, + { + "source": "/zh/deploy/agent-runtime", + "destination": "/zh/deploy/architecture" } ], "navigation": { @@ -360,8 +368,7 @@ "group": "Quick Start", "pages": [ "en/deploy/choose", - "en/deploy/docker", - "en/deploy/agent-runtime" + "en/deploy/architecture" ] }, { @@ -712,8 +719,7 @@ "group": "快速开始", "pages": [ "zh/deploy/choose", - "zh/deploy/docker", - "zh/deploy/agent-runtime" + "zh/deploy/architecture" ] }, { diff --git a/en/basic/admin-panel/ai-setting.mdx b/en/basic/admin-panel/ai-setting.mdx index f61cdd0f..715d640d 100644 --- a/en/basic/admin-panel/ai-setting.mdx +++ b/en/basic/admin-panel/ai-setting.mdx @@ -12,7 +12,7 @@ The **AI Settings** page is used to configure **AI Chat**, **AI fields**, **AI a ## Before you start -Before you open the AI Settings page, make sure the following items are ready. For self-hosted AI Chat and App Builder, first deploy the runtime plane — see [Agent Runtime Architecture](/en/deploy/agent-runtime). +Before you open the AI Settings page, make sure the following items are ready. For self-hosted AI Chat and App Builder, first deploy the runtime plane — see [Architecture](/en/deploy/architecture). | Requirement | Required? | Notes | |------|------|------| @@ -77,7 +77,7 @@ To let users use the microphone in AI Chat, enable voice input and enter the **O ### Configure Agent Runtime and App Builder -Self-hosted **AI Chat** and **App Builder** require Agent Runtime setup and a compatible chat model. When you use your own key (BYOK), the chat model must come from an **OpenAI Compatible** or **Anthropic** provider. Deploy the runtime plane first — see [Agent Runtime Architecture](/en/deploy/agent-runtime) and the [teable-deployment](https://github.com/teableio/teable-deployment) repository. For assistance, contact **support@teable.ai**. +Self-hosted **AI Chat** and **App Builder** require Agent Runtime setup and a compatible chat model. When you use your own key (BYOK), the chat model must come from an **OpenAI Compatible** or **Anthropic** provider. Deploy the runtime plane first — see [Architecture](/en/deploy/architecture) and the [teable-deployment](https://github.com/teableio/teable-deployment) repository. For assistance, contact **support@teable.ai**. @@ -125,7 +125,7 @@ Check the **Pending configuration** panel first to see which item is still missi -Check whether the runtime plane is deployed and connected (see [Agent Runtime Architecture](/en/deploy/agent-runtime)), including the required Teable environment variables — the deployment's doctor script verifies the whole chain. For assistance, contact **support@teable.ai**. +Check whether the runtime plane is deployed and connected (see [Architecture](/en/deploy/architecture)), including the required Teable environment variables — the deployment's doctor script verifies the whole chain. For assistance, contact **support@teable.ai**. diff --git a/en/basic/admin-panel/sandbox-agent.mdx b/en/basic/admin-panel/sandbox-agent.mdx index 4354c450..734a0e63 100644 --- a/en/basic/admin-panel/sandbox-agent.mdx +++ b/en/basic/admin-panel/sandbox-agent.mdx @@ -10,7 +10,7 @@ Path: Admin Panel → Sandbox Agent The **Sandbox Agent** page contains **Settings** and **Sandboxes** tabs for configuring how AI Chat runs in sandboxes. -For self-hosted setup, deploy the full-featured runtime plane first — see [Agent Runtime Architecture](/en/deploy/agent-runtime). +For self-hosted setup, deploy the full-featured runtime plane first — see [Architecture](/en/deploy/architecture). ## Runtime Limits diff --git a/en/basic/ai/app-builder.mdx b/en/basic/ai/app-builder.mdx index b1bca78b..efb055fd 100644 --- a/en/basic/ai/app-builder.mdx +++ b/en/basic/ai/app-builder.mdx @@ -231,7 +231,7 @@ For prompt patterns, build tips, rollback advice, and common troubleshooting, se - If you are running Teable self-hosted, follow the setup on the [AI Settings](/en/basic/admin-panel/ai-setting) page. If App Builder uses your own key (BYOK), the chat model must come from an **OpenAI Compatible** or **Anthropic** provider. Agent Runtime deployment is still in an early stage and can be complex. Contact Teable at **support@teable.ai** for deployment support. + If you are running Teable self-hosted, follow the setup on the [AI Settings](/en/basic/admin-panel/ai-setting) page. If App Builder uses your own key (BYOK), the chat model must come from an **OpenAI Compatible** or **Anthropic** provider. For deployment support, contact Teable at **support@teable.ai**. Before publishing, make sure both your Teable instance and object storage (MinIO / S3) are publicly accessible. If you are unsure, use **Test Public Access** on the [AI Settings](/en/basic/admin-panel/ai-setting) page. diff --git a/en/deploy/activate.mdx b/en/deploy/activate.mdx index f4faf230..3a9e60f0 100644 --- a/en/deploy/activate.mdx +++ b/en/deploy/activate.mdx @@ -25,7 +25,7 @@ Before subscribing to a plan, you need to have Teable installed and running on y If you haven't installed Teable yet, please follow one of these installation guides: - [Docker Deployment](/en/deploy/docker) (Recommended for quick setup) -- [Full-featured platform](/en/deploy/agent-runtime) (AI features and App Builder, Docker or Kubernetes) +- [Full-featured platform](/en/deploy/architecture) (AI features and App Builder, Docker or Kubernetes) Make sure your Teable instance is up and running before proceeding to the next steps. diff --git a/en/deploy/agent-runtime.mdx b/en/deploy/architecture.mdx similarity index 65% rename from en/deploy/agent-runtime.mdx rename to en/deploy/architecture.mdx index ff08e224..71211fde 100644 --- a/en/deploy/agent-runtime.mdx +++ b/en/deploy/architecture.mdx @@ -1,49 +1,54 @@ --- -title: "Agent Runtime Architecture" -description: "How the self-hosted AI runtime plane works: sandboxes, App Builder, deployed apps, domains, and versioning." +title: "Architecture" +description: "What a self-hosted Teable deployment runs: the app, its data services, and the runtime plane behind AI sessions, App Builder, and deployed apps." --- -Available for self-hosted Business plan and above - -Teable's AI features — **AI Chat**, **App Builder**, and **app deployments** — -run on a dedicated **runtime plane** that you self-host next to the Teable app. -This page explains the architecture. The deployable assets (compose files, Helm -chart, values) live in +Teable is an AI product: **AI Chat**, **App Builder**, and **app deployments** +are part of the platform, not bolt-ons. A self-hosted deployment therefore +runs the Teable app and its data services together with the **runtime plane** +that powers those AI features. This page explains how the pieces fit +together. The deployable assets (compose files, Helm chart, values) live in [teableio/teable-deployment](https://github.com/teableio/teable-deployment) and are the single source of truth — nothing on this page needs to be copied. -## Two planes - -A full-featured deployment is two planes working together: - -| Plane | What runs there | -|---|---| -| **App plane** | The Teable app itself, with its PostgreSQL and Redis | -| **Runtime plane** | Everything that powers the AI features (below) | +AI features are available for self-hosted Business plan and above. -The runtime plane consists of five services: +## What a deployment runs | Service | Purpose | |---|---| +| **Teable app** | Web UI, API, automations, AI chat — a single image: `ghcr.io/teableio/teable` | +| **PostgreSQL** | The system of record: tables, views, metadata | +| **Redis** | Cache, queues, realtime collaboration | +| **Object storage** | S3-compatible, three buckets: **public** (avatars and public assets), **private** (attachments), **build artifacts** (App Builder output) | +| **Infra Service** | Console + API — the one door the Teable app talks to; orchestrates builds and app deployments | | **Sandbox engine** | Runs every AI session inside an isolated sandbox container | -| **Infra Service** | Console + API the Teable app talks to; orchestrates builds and app deployments | | **Git registry** | Source of truth for the apps you build (App Builder pushes here) | -| **Object storage** | Attachments and build artifacts (S3-compatible) | | **Preview gateway** | Serves sandbox previews and deployed apps in the browser | +The last four services form the **runtime plane**; the deployment assets set +all of this up as one platform. + +## How it fits together + ```mermaid graph LR - subgraph "App plane" + subgraph "App & data" T["Teable app"] + P[("PostgreSQL")] + R[("Redis")] + S[("Object storage")] end subgraph "Runtime plane" I["Infra Service"] E["Sandbox engine"] G["Git registry"] - S["Object storage"] W["Preview gateway"] end U["Browser"] --> T + T --> P + T --> R + T -- "attachments" --> S T -- "AI session" --> I --> E E -- "build & push" --> G E -- "artifacts" --> S @@ -53,9 +58,12 @@ graph LR style T fill:#0D9373,stroke:#0a7a5e,color:#fff ``` -An AI chat session runs inside a sandbox; App Builder builds inside the same -sandbox, pushes source to the git registry, stores the build artifact in object -storage, and the deployed app is served through the gateway. +The Teable app reaches the runtime plane through **one connection**: the Infra +Service (`TEABLE_INFRA_API_URL` / `TEABLE_INFRA_API_KEY`); everything behind it +is internal. An AI chat session runs inside a sandbox; App Builder builds +inside the same sandbox, pushes source to the git registry, stores the build +artifact in object storage, and the deployed app is served through the +gateway. ## One domain, four DNS records @@ -82,8 +90,8 @@ You never pull or pin the agent manually. ## Versioning -The runtime plane ships as **platform releases** (`v..`) of -the deployment repository: +The platform ships as **platform releases** (`v..`) of the +deployment repository: - A **tag** is a verified snapshot: `versions.yaml` pins every component image (with digests), and the repository's `CHANGELOG.md` says what changed and @@ -99,7 +107,7 @@ declare the app compatibility window; the doctor checks it for you. ## Deploy it -Both paths install the same platform and are covered end to end in the +Both paths install the whole platform and are covered end to end in the deployment repository: @@ -111,6 +119,10 @@ deployment repository:
+Don't need AI? You can run just the app with PostgreSQL, Redis, and storage — +a **standalone** deployment ([Docker Deployment](/en/deploy/docker)) — and +attach the runtime plane later with your data in place. + Related topics, all maintained in the deployment repository: - **Already running standalone Teable?** Your data stays in place — the runtime diff --git a/en/deploy/choose.mdx b/en/deploy/choose.mdx index 5f5eefac..e1f0e46b 100644 --- a/en/deploy/choose.mdx +++ b/en/deploy/choose.mdx @@ -5,14 +5,14 @@ description: "Pick between Teable Cloud, standalone self-hosting, and the full-f Three ways to run Teable — pick by what you need: -| | **Teable Cloud** | **Standalone self-host** | **Full-featured self-host** | +| | **Teable Cloud** | **Full-featured self-host** | **Standalone self-host** | |---|---|---|---| | Tables, collaboration, API, automation | ✅ | ✅ | ✅ | -| AI features (chat, agents) | ✅ | ❌ | ✅ | -| App Builder (build & deploy apps) | ✅ | ❌ | ✅ | -| Sandboxes / previews | ✅ | ❌ | ✅ | -| Runs on | [teable.ai](https://teable.ai) — nothing to deploy | one machine: app + PostgreSQL | one machine (Docker) or a Kubernetes cluster | -| Start here | [teable.ai](https://teable.ai) | [Docker Deployment](/en/deploy/docker) | [Agent Runtime Architecture](/en/deploy/agent-runtime) → [teableio/teable-deployment](https://github.com/teableio/teable-deployment) | +| AI features (chat, agents) | ✅ | ✅ | ❌ | +| App Builder (build & deploy apps) | ✅ | ✅ | ❌ | +| Sandboxes / previews | ✅ | ✅ | ❌ | +| Runs on | [teable.ai](https://teable.ai) — nothing to deploy | one machine (Docker) or a Kubernetes cluster | one machine: app + PostgreSQL | +| Start here | [teable.ai](https://teable.ai) | [Architecture](/en/deploy/architecture) → [teableio/teable-deployment](https://github.com/teableio/teable-deployment) | [Docker Deployment](/en/deploy/docker) | Standalone and full-featured are not a fork in the road: the full-featured @@ -20,3 +20,27 @@ platform **attaches a runtime plane next to your existing standalone Teable** your data stays in place. Start standalone today, add AI later: [migration guide](https://github.com/teableio/teable-deployment/blob/main/migration/2026-07-basic-to-full-featured.md). + +## Let an AI agent deploy it + +The deployment repository is written to be driven by AI agents. Point your +agent harness (Claude Code, Codex, …) at +[teableio/teable-deployment](https://github.com/teableio/teable-deployment), +tell it which environment you're deploying to, and it can take the deployment +end to end. Prepare three things: + +1. **A domain** — everything derives from one base domain, e.g. + `teable.example.com`. +2. **A DNS token** — an API token for the platform hosting your DNS + (Cloudflare, Route 53, …), so DNS records and TLS certificates can be set + up automatically. Scope it to that one zone. +3. **Access to the deployment target:** + +| Target | What to hand the agent | +|---|---| +| A cloud server — Docker all-in-one | SSH access to the machine | +| A managed cluster on AWS (EKS) / GCP (GKE) / Azure (AKS) | Create the cluster, then hand over its `kubeconfig` | +| Any existing Kubernetes cluster | A `kubeconfig` that can install Helm releases | + +That's all it takes — the repository's guides, doctor scripts, and version +manifests carry the agent through the rest. diff --git a/en/deploy/docker.mdx b/en/deploy/docker.mdx index 92547fc0..8bf0c414 100644 --- a/en/deploy/docker.mdx +++ b/en/deploy/docker.mdx @@ -4,7 +4,7 @@ title: "Docker Deployment" **Docker Compose is recommended for small teams and evaluation** (< 50 users). -Not sure this is the right path? Start from [Choose Your Path](/en/deploy/choose). Need AI features or App Builder? Deploy the [full-featured platform](/en/deploy/agent-runtime) instead. +Not sure this is the right path? Start from [Choose Your Path](/en/deploy/choose). Need AI features or App Builder? Deploy the [full-featured platform](/en/deploy/architecture) instead. ## Docker Compose Deployment diff --git a/en/deploy/env.mdx b/en/deploy/env.mdx index b23d4384..53537667 100644 --- a/en/deploy/env.mdx +++ b/en/deploy/env.mdx @@ -4,12 +4,6 @@ description: "Here are all available environment variables in Teable and their e mode: "wide" --- -These are the **Teable app's own** environment variables. The services of the -full-featured runtime plane (sandbox engine, Infra Service, git registry, …) -are configured separately by the deployment assets in -[teableio/teable-deployment](https://github.com/teableio/teable-deployment) — -see [Agent Runtime Architecture](/en/deploy/agent-runtime). - | Environment Variable | Description | Default Value | Required | Example | | ----------------------------------------- | -------------------------------------------------------------------------- | --------------- | -------- | ------------------------------------------------- | | **Core Configuration** | | | | | diff --git a/en/deploy/upgrade.mdx b/en/deploy/upgrade.mdx index 71ed94c3..417cb2e0 100644 --- a/en/deploy/upgrade.mdx +++ b/en/deploy/upgrade.mdx @@ -61,7 +61,7 @@ production: you choose exactly when versions change. ## Full-featured deployments (AI runtime plane) A full-featured deployment (see -[Agent Runtime Architecture](/en/deploy/agent-runtime)) has **two version +[Architecture](/en/deploy/architecture)) has **two version lines**: - **The Teable app** — the date-based tags above; upgrade it as described in diff --git a/zh/basic/admin-panel/ai-setting.mdx b/zh/basic/admin-panel/ai-setting.mdx index d8a32153..68e79142 100644 --- a/zh/basic/admin-panel/ai-setting.mdx +++ b/zh/basic/admin-panel/ai-setting.mdx @@ -12,7 +12,7 @@ description: "为私有化部署实例配置 AI 对话、AI 字段、AI 自动 ## 开始前的准备 -在进入 AI 配置页之前,先确认下面这些准备项已经就绪。私有化部署如需使用 AI 对话和应用构建器,请先部署运行平面,见 [Agent Runtime 架构](/zh/deploy/agent-runtime)。 +在进入 AI 配置页之前,先确认下面这些准备项已经就绪。私有化部署如需使用 AI 对话和应用构建器,请先部署运行平面,见 [架构](/zh/deploy/architecture)。 | 准备项 | 是否必需 | 说明 | |------|------|------| @@ -77,7 +77,7 @@ description: "为私有化部署实例配置 AI 对话、AI 字段、AI 自动 ### 配置 Agent Runtime 与应用构建器 -私有化部署如需使用 **AI 对话** 和 **应用构建器**,需要先完成 Agent Runtime 配置,并配置兼容的聊天模型。使用自带 Key 时,聊天模型必须来自 **OpenAI Compatible**(OpenAI 兼容)或 **Anthropic** 类型的提供商。请先部署运行平面 —— 见 [Agent Runtime 架构](/zh/deploy/agent-runtime) 与 [teable-deployment](https://github.com/teableio/teable-deployment) 仓库。如需协助,请联系 **support@teable.ai**。 +私有化部署如需使用 **AI 对话** 和 **应用构建器**,需要先完成 Agent Runtime 配置,并配置兼容的聊天模型。使用自带 Key 时,聊天模型必须来自 **OpenAI Compatible**(OpenAI 兼容)或 **Anthropic** 类型的提供商。请先部署运行平面 —— 见 [架构](/zh/deploy/architecture) 与 [teable-deployment](https://github.com/teableio/teable-deployment) 仓库。如需协助,请联系 **support@teable.ai**。 @@ -125,7 +125,7 @@ description: "为私有化部署实例配置 AI 对话、AI 字段、AI 自动 -请检查运行平面是否已部署并接通(见 [Agent Runtime 架构](/zh/deploy/agent-runtime)),以及所需的 Teable 环境变量是否配置 —— 部署仓库自带的 doctor 脚本可以整链验证。如需协助,请联系 **support@teable.ai**。 +请检查运行平面是否已部署并接通(见 [架构](/zh/deploy/architecture)),以及所需的 Teable 环境变量是否配置 —— 部署仓库自带的 doctor 脚本可以整链验证。如需协助,请联系 **support@teable.ai**。 diff --git a/zh/basic/admin-panel/sandbox-agent.mdx b/zh/basic/admin-panel/sandbox-agent.mdx index 8f9827a1..c0de5239 100644 --- a/zh/basic/admin-panel/sandbox-agent.mdx +++ b/zh/basic/admin-panel/sandbox-agent.mdx @@ -10,7 +10,7 @@ description: "配置和管理沙箱 Agent。" **沙箱 Agent** 页面包含 **设置** 和 **沙箱** 两个页签,用于配置 AI 对话的沙箱运行方式。 -私有化部署请先部署全功能运行平面,见 [Agent Runtime 架构](/zh/deploy/agent-runtime)。 +私有化部署请先部署全功能运行平面,见 [架构](/zh/deploy/architecture)。 ## 运行限制 diff --git a/zh/basic/ai/app-builder.mdx b/zh/basic/ai/app-builder.mdx index 9048cf36..66be4e77 100644 --- a/zh/basic/ai/app-builder.mdx +++ b/zh/basic/ai/app-builder.mdx @@ -246,7 +246,7 @@ Teable 自动跟踪每一次部署。 - 如果您使用私有化部署的 Teable,请参考 [AI 配置](/zh/basic/admin-panel/ai-setting) 页面完成配置。如需让应用构建器使用自带 Key,聊天模型必须来自 **OpenAI Compatible**(OpenAI 兼容)或 **Anthropic** 类型的提供商。Agent Runtime 的私有化部署仍处于早期阶段,配置流程较繁琐;请联系 Teable:**support@teable.ai**,获取部署协助。 + 如果您使用私有化部署的 Teable,请参考 [AI 配置](/zh/basic/admin-panel/ai-setting) 页面完成配置。如需让应用构建器使用自带 Key,聊天模型必须来自 **OpenAI Compatible**(OpenAI 兼容)或 **Anthropic** 类型的提供商。如需部署协助,请联系 Teable:**support@teable.ai**。 发布前,请先确认 Teable 和对象存储(MinIO / S3)都能被公网访问;不确定时,可以在 [AI 配置](/zh/basic/admin-panel/ai-setting) 页面使用 **测试公网访问** 进行验证。 diff --git a/zh/deploy/activate.mdx b/zh/deploy/activate.mdx index d942392e..b3c6bbd5 100644 --- a/zh/deploy/activate.mdx +++ b/zh/deploy/activate.mdx @@ -25,7 +25,7 @@ Teable 为自托管部署提供了不同的订阅计划,每个计划都有其 如果您还未安装 Teable,请按照以下安装指南之一进行操作: - [Docker 部署](/zh/deploy/docker)(推荐快速安装) -- [全功能平台](/zh/deploy/agent-runtime)(AI 能力与 App Builder,Docker 或 Kubernetes) +- [全功能平台](/zh/deploy/architecture)(AI 能力与 App Builder,Docker 或 Kubernetes) 在进行下一步之前,请确保您的 Teable 实例已成功启动并运行。 diff --git a/zh/deploy/agent-runtime.mdx b/zh/deploy/architecture.mdx similarity index 67% rename from zh/deploy/agent-runtime.mdx rename to zh/deploy/architecture.mdx index ed19bcdb..7e9702c0 100644 --- a/zh/deploy/agent-runtime.mdx +++ b/zh/deploy/architecture.mdx @@ -1,47 +1,52 @@ --- -title: "Agent Runtime 架构" -description: "自部署 AI 运行面的工作原理:沙箱、App Builder、应用部署、域名模型与版本体系。" +title: "架构" +description: "私有化部署的 Teable 由什么组成:应用与数据服务,以及支撑 AI 会话、App Builder 与应用部署的运行平面。" --- -私有化部署 Business 及以上套餐可用 - -Teable 的 AI 能力 —— **AI Chat**、**App Builder** 与**应用部署** —— 运行在一个与 -Teable 应用并列自部署的**运行面**上。本页只讲架构;可部署资产(compose、Helm chart、 -values)全部在 [teableio/teable-deployment](https://github.com/teableio/teable-deployment) +Teable 本身就是 AI 产品:**AI Chat**、**App Builder** 与**应用部署**是平台的 +一部分,不是外挂。因此一套私有化部署,除了 Teable 应用与它的数据服务,还包括 +支撑这些 AI 能力的**运行平面**。本页讲各部分如何协同;可部署资产(compose、 +Helm chart、values)全部在 +[teableio/teable-deployment](https://github.com/teableio/teable-deployment) 仓库中维护,那里是唯一事实源 —— 本页没有任何需要复制的内容。 -## 两个平面 - -一套全功能部署由两个平面组成: - -| 平面 | 内容 | -|---|---| -| **应用平面** | Teable 应用本体,及其 PostgreSQL 与 Redis | -| **运行平面** | 支撑全部 AI 能力的服务(见下) | +AI 能力面向私有化部署 Business 及以上套餐提供。 -运行平面包含五个服务: +## 一套部署包含什么 | 服务 | 职责 | |---|---| +| **Teable 应用** | Web 界面、API、自动化、AI 对话 —— 单一镜像:`ghcr.io/teableio/teable` | +| **PostgreSQL** | 数据事实源:表、视图、元数据 | +| **Redis** | 缓存、队列、实时协作 | +| **对象存储** | S3 兼容,三个桶:**public**(头像与公开资源)、**private**(附件)、**build artifacts**(App Builder 构建产物) | +| **Infra Service** | 控制台与 API —— Teable 应用唯一的对接点;编排构建与应用部署 | | **沙箱引擎** | 每个 AI 会话都在独立的沙箱容器中运行 | -| **Infra Service** | 控制台与 API,Teable 应用的对接点;编排构建与应用部署 | | **Git 仓库服务** | 你构建的应用的源码事实源(App Builder 推送至此) | -| **对象存储** | 附件与构建产物(S3 兼容) | | **预览网关** | 在浏览器中提供沙箱预览与已部署应用的访问 | +后四个服务构成**运行平面**;部署资产会把以上全部作为一个平台一次装好。 + +## 如何协同 + ```mermaid graph LR - subgraph "应用平面" + subgraph "应用与数据" T["Teable 应用"] + P[("PostgreSQL")] + R[("Redis")] + S[("对象存储")] end subgraph "运行平面" I["Infra Service"] E["沙箱引擎"] G["Git 仓库服务"] - S["对象存储"] W["预览网关"] end U["浏览器"] --> T + T --> P + T --> R + T -- "附件" --> S T -- "AI 会话" --> I --> E E -- "构建并推送" --> G E -- "构建产物" --> S @@ -51,6 +56,8 @@ graph LR style T fill:#0D9373,stroke:#0a7a5e,color:#fff ``` +Teable 应用只通过**一个连接**触达运行平面:Infra Service +(`TEABLE_INFRA_API_URL` / `TEABLE_INFRA_API_KEY`),其余全部是平面内部细节。 AI 对话在沙箱内运行;App Builder 在同一个沙箱里构建,把源码推到 Git 仓库服务、 把构建产物存入对象存储,部署后的应用经预览网关对外提供访问。 @@ -76,7 +83,7 @@ Infra Service 预热这个镜像,首次会话不必现场拉取数 GB 镜像。 ## 版本体系 -运行平面以部署仓库的**平台版本**(`v<年>.<月>.<序号>`)形式发布: +平台以部署仓库的**平台版本**(`v<年>.<月>.<序号>`)形式发布: - **tag** 是已验证的快照:`versions.yaml` 钉定每个组件镜像(含 digest), 仓库的 `CHANGELOG.md` 写明每版变了什么、你需要做什么; @@ -89,7 +96,7 @@ Teable 应用有自己独立的发版线(日期型 tag;`latest` 为稳定通道) ## 部署 -两条路径安装的是同一套平台,部署仓库内有完整指引: +两条路径安装的是同一套完整平台,部署仓库内有完整指引: @@ -100,6 +107,10 @@ Teable 应用有自己独立的发版线(日期型 tag;`latest` 为稳定通道) +暂时不需要 AI?也可以只运行应用与 PostgreSQL、Redis、存储 —— +即**基础版**部署([Docker 部署](/zh/deploy/docker)),之后随时把运行平面 +挂到旁边,数据原地不动。 + 相关主题(均在部署仓库中维护): - **已在运行基础版 Teable?** 数据原地不动,运行平面挂在旁边即可: diff --git a/zh/deploy/choose.mdx b/zh/deploy/choose.mdx index cf64b29b..0ab89337 100644 --- a/zh/deploy/choose.mdx +++ b/zh/deploy/choose.mdx @@ -5,17 +5,37 @@ description: "在 Teable Cloud、基础版自部署与全功能自部署之间 运行 Teable 有三种方式,按需选择: -| | **Teable Cloud** | **基础版自部署** | **全功能自部署** | +| | **Teable Cloud** | **全功能自部署** | **基础版自部署** | |---|---|---|---| | 表格、协作、API、自动化 | ✅ | ✅ | ✅ | -| AI 能力(对话、智能体) | ✅ | ❌ | ✅ | -| App Builder(构建并部署应用) | ✅ | ❌ | ✅ | -| 沙箱 / 预览 | ✅ | ❌ | ✅ | -| 运行位置 | [teable.ai](https://teable.ai) —— 无需部署 | 一台机器:应用 + PostgreSQL | 一台机器(Docker)或 Kubernetes 集群 | -| 从这里开始 | [teable.ai](https://teable.ai) | [Docker 部署](/zh/deploy/docker) | [Agent Runtime 架构](/zh/deploy/agent-runtime) → [teableio/teable-deployment](https://github.com/teableio/teable-deployment) | +| AI 能力(对话、智能体) | ✅ | ✅ | ❌ | +| App Builder(构建并部署应用) | ✅ | ✅ | ❌ | +| 沙箱 / 预览 | ✅ | ✅ | ❌ | +| 运行位置 | [teable.ai](https://teable.ai) —— 无需部署 | 一台机器(Docker)或 Kubernetes 集群 | 一台机器:应用 + PostgreSQL | +| 从这里开始 | [teable.ai](https://teable.ai) | [架构](/zh/deploy/architecture) → [teableio/teable-deployment](https://github.com/teableio/teable-deployment) | [Docker 部署](/zh/deploy/docker) | 基础版与全功能不是二选一的岔路:全功能平台是**在你现有基础版 Teable 旁边挂载一个 运行平面** —— 业务数据原地不动。今天先跑基础版,以后随时加 AI: [迁移指南](https://github.com/teableio/teable-deployment/blob/main/migration/2026-07-basic-to-full-featured.md)。 + +## 交给 AI agent 部署 + +部署仓库本身就是按「可由 AI agent 驱动」来写的。把 +[teableio/teable-deployment](https://github.com/teableio/teable-deployment) +交给你的 agent harness(Claude Code、Codex 等),说明你要部署到什么环境, +它就能端到端完成部署。只需准备三样东西: + +1. **一个域名** —— 一切派生自一个基础域名,例如 `teable.example.com`; +2. **DNS 平台 token** —— 域名托管平台(阿里云解析、DNSPod、Cloudflare 等)的 + API token,用于自动创建解析记录与签发 TLS 证书,建议只授权这一个 zone; +3. **部署目标的访问权限:** + +| 目标 | 交给 agent 的权限 | +|---|---| +| 一台云服务器 —— Docker 单机全栈 | 机器的 SSH 访问 | +| 托管集群 —— 阿里云(ACK)/ 腾讯云(TKE) | 创建一个集群,把它的 `kubeconfig` 交给 agent | +| 任何现有 Kubernetes 集群 | 一份能安装 Helm release 的 `kubeconfig` | + +准备好这些就够了 —— 仓库内的指引、doctor 脚本与版本清单会带着 agent 走完其余全部。 diff --git a/zh/deploy/docker.mdx b/zh/deploy/docker.mdx index ebf586f5..570ade9c 100644 --- a/zh/deploy/docker.mdx +++ b/zh/deploy/docker.mdx @@ -6,7 +6,7 @@ description: "使用 Docker Compose 部署 Teable,并配置常用部署选项 {/* mintlify-resync: 2026-05-14 */} **Docker Compose 适合小团队和评估测试**(50 人以内)。 -不确定该选哪条路径?请从[选择部署方式](/zh/deploy/choose)开始。需要 AI 能力或 App Builder?请改用[全功能平台](/zh/deploy/agent-runtime)。 +不确定该选哪条路径?请从[选择部署方式](/zh/deploy/choose)开始。需要 AI 能力或 App Builder?请改用[全功能平台](/zh/deploy/architecture)。 ## Docker Compose 部署 diff --git a/zh/deploy/env.mdx b/zh/deploy/env.mdx index 6b2b1576..a903b1c0 100644 --- a/zh/deploy/env.mdx +++ b/zh/deploy/env.mdx @@ -4,11 +4,6 @@ description: "这里列举了Teable 所有可用的环境变量以及相关解 mode: "wide" --- -本页列出的是 **Teable 应用自身**的环境变量。全功能运行平面的各服务(沙箱引擎、 -Infra Service、Git 仓库服务等)由 -[teableio/teable-deployment](https://github.com/teableio/teable-deployment) -中的部署资产单独配置 —— 见 [Agent Runtime 架构](/zh/deploy/agent-runtime)。 - | 环境变量 | 描述 | 默认值 | 必填 | 示例 | | -------------------------------------------- | ------------------------------------------------------- | ---------------- | ---- | ------------------------------------------------ | | **核心配置** | | | | | diff --git a/zh/deploy/upgrade.mdx b/zh/deploy/upgrade.mdx index 5b30e8d0..be06b686 100644 --- a/zh/deploy/upgrade.mdx +++ b/zh/deploy/upgrade.mdx @@ -57,7 +57,7 @@ services: ## 全功能部署(AI 运行平面) -全功能部署(见 [Agent Runtime 架构](/zh/deploy/agent-runtime))有**两条版本线**: +全功能部署(见 [架构](/zh/deploy/architecture))有**两条版本线**: - **Teable 应用** —— 即上述日期型 tag,按部署仓库的指引升级; - **平台本身** —— From 8c1db8d3859420babb1020906ff80911540eb08a Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 20:12:48 +0800 Subject: [PATCH 07/12] docs(self-host): hide Standalone Dashboard page from navigation Same treatment as Docker Deployment: the page stays viewable at its URL but no longer appears in the Configuration group. Co-Authored-By: Claude Fable 5 --- docs.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs.json b/docs.json index c32828ba..31d60b01 100644 --- a/docs.json +++ b/docs.json @@ -393,8 +393,7 @@ "en/deploy/env", "en/deploy/email", "en/deploy/oidc", - "en/deploy/nginx", - "en/deploy/dashboard" + "en/deploy/nginx" ] }, { @@ -744,8 +743,7 @@ "zh/deploy/env", "zh/deploy/email", "zh/deploy/oidc", - "zh/deploy/nginx", - "zh/deploy/dashboard" + "zh/deploy/nginx" ] }, { From ece89d82ca77ebaf447df29bfe06ef889090c711 Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 20:13:25 +0800 Subject: [PATCH 08/12] docs(self-host): hide Nginx reverse-proxy page from navigation Standalone-era guide: the full-featured platform ships its own entry with automatic TLS. The page stays viewable and remains linked from the (also hidden) standalone Docker guide, where it is still relevant. Co-Authored-By: Claude Fable 5 --- docs.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs.json b/docs.json index 31d60b01..6e8677da 100644 --- a/docs.json +++ b/docs.json @@ -392,8 +392,7 @@ "pages": [ "en/deploy/env", "en/deploy/email", - "en/deploy/oidc", - "en/deploy/nginx" + "en/deploy/oidc" ] }, { @@ -742,8 +741,7 @@ "pages": [ "zh/deploy/env", "zh/deploy/email", - "zh/deploy/oidc", - "zh/deploy/nginx" + "zh/deploy/oidc" ] }, { From 30a0ac51bd330cf1dff80e92635334c1ffdc75a3 Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 20:21:36 +0800 Subject: [PATCH 09/12] docs(self-host): add local machine target to the agent-deploy table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker all-in-one local mode needs neither a domain nor a DNS token — everything runs on *.localhost with just Docker installed. Co-Authored-By: Claude Fable 5 --- en/deploy/choose.mdx | 1 + zh/deploy/choose.mdx | 1 + 2 files changed, 2 insertions(+) diff --git a/en/deploy/choose.mdx b/en/deploy/choose.mdx index e1f0e46b..e63dd2da 100644 --- a/en/deploy/choose.mdx +++ b/en/deploy/choose.mdx @@ -38,6 +38,7 @@ end to end. Prepare three things: | Target | What to hand the agent | |---|---| +| Your own machine — Docker all-in-one, `local` mode | Just Docker on the machine — no domain or DNS token needed (everything runs on `*.localhost`) | | A cloud server — Docker all-in-one | SSH access to the machine | | A managed cluster on AWS (EKS) / GCP (GKE) / Azure (AKS) | Create the cluster, then hand over its `kubeconfig` | | Any existing Kubernetes cluster | A `kubeconfig` that can install Helm releases | diff --git a/zh/deploy/choose.mdx b/zh/deploy/choose.mdx index 0ab89337..dd023755 100644 --- a/zh/deploy/choose.mdx +++ b/zh/deploy/choose.mdx @@ -34,6 +34,7 @@ description: "在 Teable Cloud、基础版自部署与全功能自部署之间 | 目标 | 交给 agent 的权限 | |---|---| +| 本机 —— Docker 单机全栈 `local` 模式 | 本机装好 Docker 即可 —— 无需域名与 DNS token(全部走 `*.localhost`) | | 一台云服务器 —— Docker 单机全栈 | 机器的 SSH 访问 | | 托管集群 —— 阿里云(ACK)/ 腾讯云(TKE) | 创建一个集群,把它的 `kubeconfig` 交给 agent | | 任何现有 Kubernetes 集群 | 一份能安装 Helm release 的 `kubeconfig` | From b40bf7ea71d309d20d6c20021ce7fb94930f75d6 Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 20:25:10 +0800 Subject: [PATCH 10/12] docs(self-host): recenter architecture story on the real workloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'How it fits together' section was foregrounding git/artifact plumbing. Rewrite it around what actually consumes the machine: sandboxes (one per AI session, bursty — size by peak concurrent sessions) and deployed apps (long-lived containers that accumulate). Git registry and object storage become dotted supporting edges; deployed apps get their own node. Co-Authored-By: Claude Fable 5 --- en/deploy/architecture.mdx | 38 ++++++++++++++++++++++++++++---------- zh/deploy/architecture.mdx | 32 ++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/en/deploy/architecture.mdx b/en/deploy/architecture.mdx index 71211fde..5cb79e99 100644 --- a/en/deploy/architecture.mdx +++ b/en/deploy/architecture.mdx @@ -33,6 +33,7 @@ all of this up as one platform. ```mermaid graph LR + U["Browser"] subgraph "App & data" T["Teable app"] P[("PostgreSQL")] @@ -41,29 +42,46 @@ graph LR end subgraph "Runtime plane" I["Infra Service"] - E["Sandbox engine"] + E["Sandboxes — one per AI session"] + A["Deployed apps — one container each"] G["Git registry"] W["Preview gateway"] end - U["Browser"] --> T + U --> T T --> P T --> R T -- "attachments" --> S - T -- "AI session" --> I --> E - E -- "build & push" --> G - E -- "artifacts" --> S - U -- "previews & deployed apps" --> W + T -- "AI sessions" --> I + I -- "starts" --> E + I -- "deploys" --> A + E -. "source" .-> G + E -. "artifacts" .-> S + U -- "sandbox previews" --> W + U -- "deployed apps" --> W W --> E + W --> A style T fill:#0D9373,stroke:#0a7a5e,color:#fff + style E fill:#F59E0B,stroke:#b45309,color:#fff + style A fill:#F59E0B,stroke:#b45309,color:#fff ``` The Teable app reaches the runtime plane through **one connection**: the Infra Service (`TEABLE_INFRA_API_URL` / `TEABLE_INFRA_API_KEY`); everything behind it -is internal. An AI chat session runs inside a sandbox; App Builder builds -inside the same sandbox, pushes source to the git registry, stores the build -artifact in object storage, and the deployed app is served through the -gateway. +is internal. What the plane actually runs is two kinds of workload: + +- **Sandboxes** — every AI chat or App Builder session gets its own isolated + container, started on demand and destroyed when the session ends. This is + the dominant, bursty load: size your machine by **peak concurrent AI + sessions**, not by user count (per-sandbox limits are configurable in the + admin panel). +- **Deployed apps** — every app someone ships runs as its own long-lived + container behind `*.app.`. Sandboxes come and go; deployed apps + accumulate and keep serving. + +The rest is plumbing: building happens inside the session's sandbox, the git +registry and object storage persist what it produces (source and build +artifacts), and the gateway routes browsers to the right sandbox or app. ## One domain, four DNS records diff --git a/zh/deploy/architecture.mdx b/zh/deploy/architecture.mdx index 7e9702c0..78953fb3 100644 --- a/zh/deploy/architecture.mdx +++ b/zh/deploy/architecture.mdx @@ -31,6 +31,7 @@ Helm chart、values)全部在 ```mermaid graph LR + U["浏览器"] subgraph "应用与数据" T["Teable 应用"] P[("PostgreSQL")] @@ -39,27 +40,42 @@ graph LR end subgraph "运行平面" I["Infra Service"] - E["沙箱引擎"] + E["沙箱 —— 每个 AI 会话一个"] + A["已部署应用 —— 每个一个容器"] G["Git 仓库服务"] W["预览网关"] end - U["浏览器"] --> T + U --> T T --> P T --> R T -- "附件" --> S - T -- "AI 会话" --> I --> E - E -- "构建并推送" --> G - E -- "构建产物" --> S - U -- "预览与已部署应用" --> W + T -- "AI 会话" --> I + I -- "启动" --> E + I -- "部署" --> A + E -. "源码" .-> G + E -. "构建产物" .-> S + U -- "沙箱预览" --> W + U -- "已部署应用" --> W W --> E + W --> A style T fill:#0D9373,stroke:#0a7a5e,color:#fff + style E fill:#F59E0B,stroke:#b45309,color:#fff + style A fill:#F59E0B,stroke:#b45309,color:#fff ``` Teable 应用只通过**一个连接**触达运行平面:Infra Service (`TEABLE_INFRA_API_URL` / `TEABLE_INFRA_API_KEY`),其余全部是平面内部细节。 -AI 对话在沙箱内运行;App Builder 在同一个沙箱里构建,把源码推到 Git 仓库服务、 -把构建产物存入对象存储,部署后的应用经预览网关对外提供访问。 +平面上真正运行的,是两类负载: + +- **沙箱** —— 每个 AI 对话、每次 App Builder 构建会话,都按需启动一个独立 + 容器,会话结束即销毁。这是平台最主要且突发性的负载:机器规格要按 + **AI 会话峰值并发**来估,而不是按用户数(单个沙箱的资源限额可在管理面板配置)。 +- **已部署应用** —— 每个被部署出去的应用都是一个长驻容器,经 `*.app.` + 对外服务。沙箱来了又走,已部署应用则不断累积、持续运行。 + +其余都是配套管线:构建在会话的沙箱内完成,Git 仓库服务与对象存储负责沉淀 +它产出的源码与构建产物,网关把浏览器路由到对应的沙箱或应用。 ## 一个域名,四条 DNS 记录 From 852d87a2155e101de51d5548e081a8de50792d1c Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 20:25:10 +0800 Subject: [PATCH 11/12] docs(self-host): rename Choose Your Path page to Deployment Title-only rename (slug and redirects unchanged); update the two inbound link texts in the Docker guide. Co-Authored-By: Claude Fable 5 --- en/deploy/choose.mdx | 2 +- en/deploy/docker.mdx | 2 +- zh/deploy/choose.mdx | 2 +- zh/deploy/docker.mdx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/en/deploy/choose.mdx b/en/deploy/choose.mdx index e63dd2da..4eec8d20 100644 --- a/en/deploy/choose.mdx +++ b/en/deploy/choose.mdx @@ -1,5 +1,5 @@ --- -title: "Choose Your Path" +title: "Deployment" description: "Pick between Teable Cloud, standalone self-hosting, and the full-featured self-hosted platform." --- diff --git a/en/deploy/docker.mdx b/en/deploy/docker.mdx index 8bf0c414..9113b217 100644 --- a/en/deploy/docker.mdx +++ b/en/deploy/docker.mdx @@ -4,7 +4,7 @@ title: "Docker Deployment" **Docker Compose is recommended for small teams and evaluation** (< 50 users). -Not sure this is the right path? Start from [Choose Your Path](/en/deploy/choose). Need AI features or App Builder? Deploy the [full-featured platform](/en/deploy/architecture) instead. +Not sure this is the right path? Start from [Deployment](/en/deploy/choose). Need AI features or App Builder? Deploy the [full-featured platform](/en/deploy/architecture) instead. ## Docker Compose Deployment diff --git a/zh/deploy/choose.mdx b/zh/deploy/choose.mdx index dd023755..56f0497a 100644 --- a/zh/deploy/choose.mdx +++ b/zh/deploy/choose.mdx @@ -1,5 +1,5 @@ --- -title: "选择部署方式" +title: "部署" description: "在 Teable Cloud、基础版自部署与全功能自部署之间做选择。" --- diff --git a/zh/deploy/docker.mdx b/zh/deploy/docker.mdx index 570ade9c..093e4227 100644 --- a/zh/deploy/docker.mdx +++ b/zh/deploy/docker.mdx @@ -6,7 +6,7 @@ description: "使用 Docker Compose 部署 Teable,并配置常用部署选项 {/* mintlify-resync: 2026-05-14 */} **Docker Compose 适合小团队和评估测试**(50 人以内)。 -不确定该选哪条路径?请从[选择部署方式](/zh/deploy/choose)开始。需要 AI 能力或 App Builder?请改用[全功能平台](/zh/deploy/architecture)。 +不确定该选哪条路径?请先看[部署](/zh/deploy/choose)页。需要 AI 能力或 App Builder?请改用[全功能平台](/zh/deploy/architecture)。 ## Docker Compose 部署 From b3b68a800835b5531070d30caf976183731cff00 Mon Sep 17 00:00:00 2001 From: Bieber Date: Wed, 15 Jul 2026 20:36:16 +0800 Subject: [PATCH 12/12] docs(self-host): reorganize upgrade page around the changelog-date logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the standalone-first framing. Routine updates: read the changelog, then move the Teable image to any release dated at or after the entry — Docker can ride latest, Kubernetes pins and bumps deliberately. Best practice: use VERSIONS.md as the upgrade sheet each round — newest platform release plus the concrete tag latest corresponds to, verified by the doctor. Co-Authored-By: Claude Fable 5 --- en/deploy/upgrade.mdx | 98 +++++++++++++++++++------------------------ zh/deploy/upgrade.mdx | 83 ++++++++++++++++-------------------- 2 files changed, 79 insertions(+), 102 deletions(-) diff --git a/en/deploy/upgrade.mdx b/en/deploy/upgrade.mdx index 417cb2e0..cd0894c5 100644 --- a/en/deploy/upgrade.mdx +++ b/en/deploy/upgrade.mdx @@ -24,61 +24,49 @@ All tags are listed on Before performing any upgrade, we strongly recommend backing up your data first. -## Standalone: Docker Compose upgrade - -If you run standalone Teable with Docker Compose: - -```bash -cd teable # your deployment directory -docker compose pull -docker compose up -d -``` - -Containers are recreated on the new image; your data lives in Docker volumes -(or your external database) and is not touched. - -Verify: - -```bash -docker compose ps -docker compose logs -f teable -``` - -### Pin a specific version - -To run a specific release instead of the `latest` channel, set the image tag in -`docker-compose.yaml`: - -```yaml -services: - teable: - image: ghcr.io/teableio/teable:release.2026-07-14T12-24-39Z.2228 -``` - -then `docker compose pull && docker compose up -d`. Pinning is recommended for -production: you choose exactly when versions change. - -## Full-featured deployments (AI runtime plane) - -A full-featured deployment (see -[Architecture](/en/deploy/architecture)) has **two version -lines**: - -- **The Teable app** — the date-based tags above; upgrade it as described in - the deployment repository's guides. -- **The platform** — the - [teableio/teable-deployment](https://github.com/teableio/teable-deployment) - repository ships **platform releases** (`v..`): each git - tag is a verified snapshot of every runtime component, with - [`CHANGELOG.md`](https://github.com/teableio/teable-deployment/blob/main/CHANGELOG.md) - entries stating what changed and what you must do (most releases are - hot-swappable). Upgrade **from the repository checked out at the release - tag**, following - [`VERSIONS.md`](https://github.com/teableio/teable-deployment/blob/main/VERSIONS.md). - -After upgrading either line, run the bundled **doctor** — it checks health and -compares what is actually running against the platform release manifest -(compatible / upgrade the Teable app / unknown combination). +## Routine updates: follow the changelog + +New features and fixes are announced in the [Changelog](/en/changelog), and +every Teable release tag carries its build date — so "do I have this yet?" is +just a date comparison. To pick up something you read about, move your Teable +image to any release dated at or after that entry: + +- **Docker deployments** can simply ride the `latest` channel: + + ```bash + cd teable # your deployment directory + docker compose pull + docker compose up -d + ``` + + Containers are recreated on the new image; your data lives in Docker volumes + (or your external database) and is not touched. + +- **Kubernetes deployments** should not run a floating tag: keep the Teable + image pinned and **bump the pinned tag deliberately on each update**. The + deployment repository's `pin-image.sh` resolves which concrete release + `latest` currently points to. + +## Best practice: upgrade the whole platform together + +A full-featured deployment has two version lines — the Teable app and the +platform itself (see [Architecture](/en/deploy/architecture)). The most +reliable routine ties them together, with +[`VERSIONS.md`](https://github.com/teableio/teable-deployment/blob/main/VERSIONS.md) +as the upgrade sheet. On each round: + +1. Upgrade the runtime plane to the **newest platform release** + (`v..`): each git tag is a verified snapshot of every + runtime component, and its + [`CHANGELOG.md`](https://github.com/teableio/teable-deployment/blob/main/CHANGELOG.md) + entry states what changed and what, if anything, you must do (most releases + are hot-swappable). Work from the repository checked out at that tag. +2. Move the Teable app to the **concrete release tag that `latest` currently + corresponds to** (`pin-image.sh` resolves it) — a pinned, verified + combination instead of a floating channel. +3. Run the bundled **doctor** — it checks health and compares what is actually + running against the platform release manifest (compatible / upgrade the + Teable app / unknown combination). ## Database migration diff --git a/zh/deploy/upgrade.mdx b/zh/deploy/upgrade.mdx index be06b686..e1b56c1f 100644 --- a/zh/deploy/upgrade.mdx +++ b/zh/deploy/upgrade.mdx @@ -23,53 +23,42 @@ Teable 应用以**日期型 release tag** 发布,形如 在进行任何更新操作之前,强烈建议先备份您的数据。 -## 基础版:Docker Compose 更新 - -如果您使用 Docker Compose 部署基础版 Teable: - -```bash -cd teable # 您的部署目录 -docker compose pull -docker compose up -d -``` - -容器将基于新镜像重建;数据存储在 Docker 卷(或外部数据库)中,不受影响。 - -验证: - -```bash -docker compose ps -docker compose logs -f teable -``` - -### 钉定特定版本 - -如需运行特定版本而非 `latest` 通道,在 `docker-compose.yaml` 中指定镜像 tag: - -```yaml -services: - teable: - image: ghcr.io/teableio/teable:release.2026-07-14T12-24-39Z.2228 -``` - -然后执行 `docker compose pull && docker compose up -d`。 -生产环境建议钉版:版本何时变化完全由您掌控。 - -## 全功能部署(AI 运行平面) - -全功能部署(见 [架构](/zh/deploy/architecture))有**两条版本线**: - -- **Teable 应用** —— 即上述日期型 tag,按部署仓库的指引升级; -- **平台本身** —— - [teableio/teable-deployment](https://github.com/teableio/teable-deployment) - 仓库以**平台版本**(`v<年>.<月>.<序号>`)发布:每个 git tag 是全部运行面组件的 - 已验证快照,[`CHANGELOG.md`](https://github.com/teableio/teable-deployment/blob/main/CHANGELOG.md) - 写明每版变了什么、你需要做什么(多数版本可热切换)。升级时**从 checkout 到 - 对应 tag 的仓库副本操作**,参照 - [`VERSIONS.md`](https://github.com/teableio/teable-deployment/blob/main/VERSIONS.md)。 - -任一条线升级后,运行自带的 **doctor** —— 它检查健康状态,并把实际运行的组合与 -平台版本清单比对(兼容 / 需升级 Teable 应用 / 未知组合)。 +## 日常更新:跟着更新日志走 + +新功能与修复都发布在[更新日志](/zh/changelog)里,而 Teable 的每个 release tag +自带构建日期 —— 所以「我有没有这个功能」就是一次日期比较。想用上日志里提到的 +某项更新,把 Teable 镜像升到该条目日期之后(含当日)的任一版本即可: + +- **Docker 部署**直接跟 `latest` 通道就行: + + ```bash + cd teable # 您的部署目录 + docker compose pull + docker compose up -d + ``` + + 容器基于新镜像重建;数据存储在 Docker 卷(或外部数据库)中,不受影响。 + +- **Kubernetes 部署**不要跑浮动 tag:保持 Teable 镜像钉版,**每次更新时主动 + 上调钉定的版本号**。部署仓库自带的 `pin-image.sh` 可解析 `latest` 当前 + 对应的具体版本。 + +## 最佳方式:整个平台一起升 + +全功能部署有两条版本线 —— Teable 应用与平台本身(见[架构](/zh/deploy/architecture))。 +最可靠的节奏是把两条线绑在一起,以 +[`VERSIONS.md`](https://github.com/teableio/teable-deployment/blob/main/VERSIONS.md) +为升级清单,每轮: + +1. 把运行平面升到**最新平台版本**(`v<年>.<月>.<序号>`):每个 git tag 都是 + 全部运行面组件的已验证快照,对应的 + [`CHANGELOG.md`](https://github.com/teableio/teable-deployment/blob/main/CHANGELOG.md) + 条目写明变了什么、你需要做什么(多数版本可热切换)。操作时从 checkout 到 + 该 tag 的仓库副本进行; +2. 把 Teable 应用钉到 **`latest` 当前对应的具体版本号**(`pin-image.sh` + 可解析)—— 用钉定的已验证组合替代浮动通道; +3. 跑自带的 **doctor** —— 检查健康状态,并把实际运行的组合与平台版本清单 + 比对(兼容 / 需升级 Teable 应用 / 未知组合)。 ## 数据库迁移