diff --git a/docs.json b/docs.json index 3e09f55..cf70eae 100644 --- a/docs.json +++ b/docs.json @@ -319,6 +319,7 @@ "group": "Production Deployment", "pages": [ "en/deploy/production-overview", + "en/deploy/agent-runtime", "en/deploy/k8s", "en/deploy/aws", "en/deploy/azure", @@ -682,6 +683,7 @@ "group": "生产环境部署", "pages": [ "zh/deploy/production-overview", + "zh/deploy/agent-runtime", "zh/deploy/k8s", "zh/deploy/aws", "zh/deploy/azure", diff --git a/en/basic/admin-panel/ai-setting.mdx b/en/basic/admin-panel/ai-setting.mdx index dd06a31..0a1be64 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. Self-hosted Agent Runtime deployment is still in an early stage and can be complex. Contact Teable at **support@teable.ai** for deployment support. +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). | 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 deployment is still in an early stage and can be complex. Contact Teable at **support@teable.ai** for deployment support. +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**. @@ -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 setup. Self-hosted Agent Runtime deployment is still in an early stage and can be complex. Contact Teable at **support@teable.ai** for deployment support. +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**. diff --git a/en/basic/admin-panel/sandbox-agent.mdx b/en/basic/admin-panel/sandbox-agent.mdx index 7678baf..8359932 100644 --- a/en/basic/admin-panel/sandbox-agent.mdx +++ b/en/basic/admin-panel/sandbox-agent.mdx @@ -10,6 +10,8 @@ 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. + ## Runtime Limits Configure runtime limits in the **Settings** tab: diff --git a/en/deploy/agent-runtime.mdx b/en/deploy/agent-runtime.mdx new file mode 100644 index 0000000..4819715 --- /dev/null +++ b/en/deploy/agent-runtime.mdx @@ -0,0 +1,778 @@ +--- +title: "Agent Runtime Deployment" +description: "Deploy a self-hosted Teable Agent Runtime stack on a Linux server with Docker Compose." +--- + +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. + +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. + + +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**. + + +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 | +|---|---| +| `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. + +[ingress] +mode = "direct" # Docker runtime supports direct mode only. + +[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 | +|---|---| +| 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 | +|---|---| +| `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. diff --git a/zh/basic/admin-panel/ai-setting.mdx b/zh/basic/admin-panel/ai-setting.mdx index 9cf0cbd..47223cc 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 配置页之前,先确认下面这些准备项已经就绪。Agent Runtime 的私有化部署仍处于早期阶段,配置流程较繁琐;请联系 Teable:**support@teable.ai**,获取部署协助。 +在进入 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 的私有化部署仍处于早期阶段,配置流程较繁琐;请联系 Teable:**support@teable.ai**,获取部署协助。 +私有化部署如需使用 **AI 对话** 和 **应用构建器**,需要先完成 Agent Runtime 配置,并配置兼容的聊天模型。使用自带 Key 时,聊天模型必须来自 **OpenAI Compatible**(OpenAI 兼容)或 **Anthropic** 类型的提供商。Agent Runtime 的私有化部署仍处于早期阶段,配置流程较繁琐。请按照[部署 Agent Runtime](/zh/deploy/agent-runtime)部署所需服务,该文档会根据实际部署反馈持续迭代。如需协助,请联系 **support@teable.ai**。 @@ -125,7 +125,7 @@ description: "为私有化部署实例配置 AI 对话、AI 字段、AI 自动 -请检查当前部署是否已完成 Agent Runtime 配置。Agent Runtime 的私有化部署仍处于早期阶段,配置流程较繁琐;请联系 Teable:**support@teable.ai**,获取部署协助。 +请检查当前部署是否已经按照[部署 Agent Runtime](/zh/deploy/agent-runtime)完成部署,并配置所需的 Teable 环境变量。如需协助,请联系 **support@teable.ai**。 diff --git a/zh/basic/admin-panel/sandbox-agent.mdx b/zh/basic/admin-panel/sandbox-agent.mdx index 01fed2e..776890e 100644 --- a/zh/basic/admin-panel/sandbox-agent.mdx +++ b/zh/basic/admin-panel/sandbox-agent.mdx @@ -10,6 +10,8 @@ description: "配置和管理沙箱 Agent。" **沙箱 Agent** 页面包含 **设置** 和 **沙箱** 两个页签,用于配置 AI 对话的沙箱运行方式。 +私有化部署请先完成[部署 Agent Runtime](/zh/deploy/agent-runtime),再配置此页面。 + ## 运行限制 在 **设置** 页签中配置运行限制: diff --git a/zh/deploy/agent-runtime.mdx b/zh/deploy/agent-runtime.mdx new file mode 100644 index 0000000..1294f21 --- /dev/null +++ b/zh/deploy/agent-runtime.mdx @@ -0,0 +1,773 @@ +--- +title: "部署 Agent Runtime" +description: "使用 Docker Compose 在 Linux 服务器上部署私有化 Teable Agent Runtime。" +--- + +私有化部署商业版及以上适用 + +Teable Agent Runtime 为 **AI 对话** 和 **应用构建器** 提供沙箱及应用运行时服务。本指南介绍如何使用 Docker Compose,在一台 Linux 服务器上部署完整服务栈。 + +部署内容包括 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` + +原样复制: + +```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 # 停止服务,保留数据卷 +``` + +### 升级 + +修改 `.env` 中的镜像 tag,然后执行 `docker compose pull && docker compose up -d`。 + +沙箱、应用基准镜像或 execd/egress 更新后,请按照[预拉取运行时镜像](#预拉取运行时镜像)重新拉取。 + +### 备份数据 + +建议定期备份以下数据卷,尤其是 `minio-data` 与 `caddy-data`。 + +| 卷 | 内容 | +|---|---| +| `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 配置 | + +`docker compose down -v` 会同时删除数据卷,包括制品、Git 仓库和证书。请谨慎使用。 + +## 安全与限制 + +### 安全须知 + +- 含密钥的文件只有 `.env` 与 `compose.override.yaml`,**切勿提交到版本库或外发**。 +- 整栈共用一把 `OPENSANDBOX_API_KEY`;如需分权,请分别替换各服务的对应环境变量。 +- `docker.sock` 挂载进 opensandbox-server 与 infra-service(Docker 后端必需),等价于宿主 root + 权限,请只在专用、可信的主机上部署。 +- 唯一公网面是 Caddy 的 80/443;MinIO 控制台(9001)不对外,需要时用 SSH 隧道访问。 +- 沙箱与宿主共享内核,**不是多租户强隔离边界**;对外开放多租户场景请为沙箱引擎配置 + gVisor / Kata 等强隔离运行时。 + +### 能力边界 + +**可用**:沙箱引擎全功能;应用主链路(创建 → 激活 → 网关访问 → 删除,含 dashboard、日志、 +S3 制品);git 推拉;控制台 api-key 登录 + Infra API;文件页与 S3 兼容接口。 + +**不可用 / 受限**(Docker 模式取舍):应用 `host-prefix` 改名(返回 501)、`scale-to-zero` +闲置缩容(no-op)、引擎 key 轮换 / server 滚动升级、控制台 Kubernetes 观测页(显示为空属正常)。