open-design/docs/deployment/cloud/aliyun.md
dhaksdhakshin 751dc3eb57 docs: document OD_API_TOKEN bearer-token forwarding in Nginx block
PerishCode follow-up review on PR #3275:

The Path A Nginx block as written would 401 every UI call except the
three open probes (/api/health, /api/version, /api/daemon/status). Same
root cause as the ACK fix in 9d5f6ec — the auth model affects both paths,
not just direct-container deployments.

Verified against source:
- deploy/scripts/install.sh:386 always writes a generated OD_API_TOKEN
  into deploy/.env (no opt-out flag).
- deploy/docker-compose.yml:18 requires OD_API_TOKEN (Compose ? syntax)
  and binds OD_BIND_HOST=0.0.0.0, so the daemon-side bearer middleware
  is always active for the Compose path.
- apps/daemon/src/server.ts:3777 keys the loopback short-circuit on
  isLoopbackPeerAddress(req.socket?.remoteAddress) — the TCP peer, not
  X-Forwarded-For — so a reverse-proxied request from a Docker bridge
  IP never gets the localhost bypass.

Adds proxy_set_header Authorization to the Nginx block, a paragraph
explaining where OD_API_TOKEN comes from, and updates the pitfalls row
that previously only mentioned CORS to also list the missing-bearer
cause.
2026-05-29 14:39:39 +05:30

16 KiB
Raw Blame History

Alibaba Cloud (阿里云) Deployment

This guide covers self-hosting Open Design on Alibaba Cloud for users in mainland China and the broader Asia-Pacific region. It documents the supported deployment paths, image-pull optimisations, and ICP filing considerations for public-facing instances.

Status: This is a docs-only guide. The flows below follow Alibaba Cloud's published product behaviour and the existing docs/deployment/docker.md and docs/install-guide.md container model. Live ROS templates, one-click scripts, and verification screenshots are tracked as a follow-up under issue #1025; contributions from operators with active Alibaba Cloud accounts are welcome.

Why Alibaba Cloud?

For users in mainland China, deploying Open Design to Alibaba Cloud (阿里云) instead of an overseas provider gives you:

  • Lower latency for users on China Telecom, China Unicom, and China Mobile networks.
  • Image acceleration via Alibaba Cloud Container Registry (容器镜像服务 ACR) — Docker Hub pulls from the mainland are unreliable; ACR mirrors solve this.
  • Compliant public hosting through ICP filing (备案), required for any internet-facing service on a .cn domain or a domain pointed at a mainland China IP.

If your users are outside mainland China, AWS, GCP, or Azure are typically simpler. This guide is targeted at the mainland use case.

Deployment paths

Path Best for Complexity Cost shape
A. ECS (云服务器 ECS) Single-machine self-host, small teams, evaluation Low Pay-as-you-go or subscription, fixed per VM
B. ACK (容器服务 ACK) Multi-tenant teams, autoscaling, HA Medium-High Cluster + node pool + load balancer
C. ROS (资源编排 ROS) Repeatable infra-as-code provisioning Medium Same as the underlying ECS/ACK resources

Most first-time deployments should start with Path A (ECS).

Prerequisites

  • An Alibaba Cloud account with a verified payment method.
  • For mainland China regions (e.g. cn-hangzhou, cn-shanghai, cn-beijing): a real-name verified (实名认证) account.
  • For public domains served from a mainland China region: an ICP filing for the domain.
  • Local install of the Alibaba Cloud CLI (aliyun) if you plan to script provisioning.

Path A — Deploy to ECS

This path puts Open Design on a single ECS instance using the same Docker Compose stack documented in docs/deployment/docker.md.

Step 1: Create the ECS instance

Use the Alibaba Cloud console or the CLI. A reasonable starting shape for evaluation:

Setting Recommended value Notes
Instance type ecs.t6-c1m2.large (2 vCPU / 4 GiB) Lightweight; bump to ecs.c7 for production
Image Ubuntu 24.04 LTS 64-bit Open Design's Docker image is linux/amd64 and linux/arm64
Storage 40 GiB ESSD Enough for the image, agent CWDs, and SQLite
Network VPC with a public IP or EIP Required if users will reach the instance directly
Security group Inbound 22/tcp (your IP only), outbound all Add 443/tcp only behind a reverse proxy — see Network exposure

CLI equivalent (replace placeholders):

aliyun ecs RunInstances \
  --RegionId cn-hangzhou \
  --ImageId ubuntu_24_04_x64_20G_alibase \
  --InstanceType ecs.t6-c1m2.large \
  --SecurityGroupId sg-xxxxxxxx \
  --VSwitchId vsw-xxxxxxxx \
  --InstanceChargeType PostPaid \
  --SystemDisk.Category cloud_essd \
  --SystemDisk.Size 40

Step 2: Install Docker

SSH in and install Docker Engine plus the Compose plugin:

# Aliyun's mirrored Docker install script (faster from mainland China than get.docker.com)
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER

Log out and back in so the group change takes effect.

Step 3: Configure image acceleration (镜像加速)

Pulling from docker.io directly inside mainland China is slow and intermittently fails. Configure ACR's free public mirror:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null <<EOF
{
  "registry-mirrors": [
    "https://<your-acr-prefix>.mirror.aliyuncs.com"
  ]
}
EOF
sudo systemctl restart docker

Get your personal mirror prefix from the Alibaba Cloud console under Container Registry → Image Tools → Image Accelerator (容器镜像服务 → 镜像工具 → 镜像加速器).

Step 4: Run Open Design

From this point the flow matches docs/install-guide.md:

git clone https://github.com/nexu-io/open-design.git
cd open-design
bash deploy/scripts/install.sh --non-interactive --port 7456

Step 5: Put a reverse proxy in front

Open Design binds to 127.0.0.1:7456 by design — the daemon is never directly exposed to the network. For public access, terminate TLS at Nginx or an Alibaba Cloud SLB / ALB and forward to 127.0.0.1:7456. Do not expose port 7456 directly through the security group. See the network section of docs/install-guide.md for the full rationale.

A minimal Nginx block:

server {
  listen 443 ssl http2;
  server_name design.example.cn;

  ssl_certificate     /etc/letsencrypt/live/design.example.cn/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/design.example.cn/privkey.pem;

  location / {
    proxy_pass http://127.0.0.1:7456;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    # Forward the bearer token to the daemon. The Compose stack always
    # generates an OD_API_TOKEN and binds the daemon to 0.0.0.0 inside
    # the container, so every /api/* call coming through Nginx must
    # carry Authorization: Bearer <token>. Only /api/health,
    # /api/version, and /api/daemon/status are exempt. The loopback
    # short-circuit in the daemon checks the TCP peer address, not the
    # X-Forwarded-For header, so reverse-proxy traffic never gets the
    # localhost bypass.
    proxy_set_header Authorization "Bearer <OD_API_TOKEN from deploy/.env>";
  }
}

Set OPEN_DESIGN_ALLOWED_ORIGINS in deploy/.env to the public URL so CORS clears the proxy. The OD_API_TOKEN referenced in the Nginx block above is generated automatically by deploy/scripts/install.sh and written into deploy/.env; copy that value (or wire proxy_set_header Authorization to read it from a secrets manager) so the proxy authenticates on every request.

Path B — Deploy to ACK

Container Service for Kubernetes (容器服务 ACK) is the right choice when you need:

  • Horizontal scaling beyond a single VM.
  • High availability across availability zones.
  • Standard Kubernetes tooling (kubectl, Helm) for ops.

Open Design does not yet ship an official Helm chart. The minimal manifest below is a starting point — production users should harden it (resource limits, PodDisruptionBudget, NetworkPolicy, persistent storage class).

Required env for Kubernetes: the daemon defaults to OD_BIND_HOST=127.0.0.1, which makes the readiness probe (and the Service) unable to reach the container. To make the Pod reachable inside the cluster you must set OD_BIND_HOST=0.0.0.0, and the daemon's bound-API-token guard then requires OD_API_TOKEN to be set whenever it binds to a non-loopback interface. Both env vars are reflected in the manifest below.

Also note that the daemon reads OD_ALLOWED_ORIGINS directly. The OPEN_DESIGN_ALLOWED_ORIGINS name documented in deploy/.env.example is a Compose-only alias mapped in deploy/docker-compose.yml; for the direct-container ACK path you must set OD_ALLOWED_ORIGINS instead.

Create the API-token secret first:

# Generate a random token and store it in the cluster
kubectl create secret generic open-design-secrets \
  --from-literal=api-token="$(openssl rand -hex 32)"

Then apply the manifest:

# open-design.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: open-design
spec:
  replicas: 1  # Open Design uses local SQLite; multi-replica needs shared storage
  selector:
    matchLabels: { app: open-design }
  template:
    metadata:
      labels: { app: open-design }
    spec:
      containers:
        - name: open-design
          image: registry.cn-hangzhou.aliyuncs.com/<your-namespace>/open-design:latest
          ports:
            - containerPort: 7456
          env:
            - name: OD_DATA_DIR
              value: /data
            - name: OD_BIND_HOST
              value: "0.0.0.0"            # required so the readinessProbe and Service can reach the daemon
            - name: OD_API_TOKEN
              valueFrom:
                secretKeyRef:
                  name: open-design-secrets
                  key: api-token            # required whenever OD_BIND_HOST is non-loopback
            - name: OD_ALLOWED_ORIGINS
              value: "https://design.example.cn"  # set when fronting with an Ingress; daemon reads OD_*, not OPEN_DESIGN_*
          volumeMounts:
            - name: data
              mountPath: /data
          readinessProbe:
            httpGet: { path: /api/health, port: 7456 }
            initialDelaySeconds: 10
          resources:
            requests: { cpu: 250m, memory: 384Mi }
            limits:   { cpu: "1",   memory: 768Mi }
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: open-design-data
---
apiVersion: v1
kind: Service
metadata:
  name: open-design
spec:
  type: ClusterIP
  selector: { app: open-design }
  ports:
    - port: 80
      targetPort: 7456

Apply with kubectl apply -f open-design.yaml. Front the Service with an Ingress (NGINX Ingress Controller is preinstalled on ACK Pro) and an ACM-issued certificate. With OD_API_TOKEN set, every /api/* request from non-loopback origins must carry an Authorization: Bearer <token> header — wire that into your Ingress / proxy auth layer.

Note on replicas: 1: the daemon writes SQLite at .od/app.sqlite (see AGENTS.md FAQ "Where is data written?"). Running multiple replicas without shared storage will diverge state. A multi-replica ACK topology needs an external database; that is out of scope for this guide.

Path C — ROS templates

Resource Orchestration Service (资源编排 ROS) provisions the same resources declaratively. There is no first-party ROS template for Open Design yet. Operators with Alibaba Cloud access are welcome to contribute one as a follow-up to this guide; the natural location is deploy/aliyun/ros/.

Until a template lands, treat ROS as advanced infra-as-code and use Path A or Path B above.

Image acceleration (镜像加速)

If you have not already done it during ECS setup, configure Docker's image mirror so image pulls from docker.io go through Alibaba Cloud's mirror:

  1. Log in to the Alibaba Cloud console.
  2. Open Container Registry → Image Tools → Image Accelerator (容器镜像服务 → 镜像工具 → 镜像加速器).
  3. Copy your personal accelerator URL (looks like https://<prefix>.mirror.aliyuncs.com).
  4. Add it to /etc/docker/daemon.json under registry-mirrors and restart Docker.

For ACK, image acceleration is enabled by default on the cluster's node pool — no per-node config needed.

ICP filing (备案)

Any service that serves a domain pointed at a mainland China IP, or serves a .cn domain, must complete ICP filing through the Ministry of Industry and Information Technology (工信部). Skipping this is a hard block — Alibaba Cloud will firewall HTTP/HTTPS on unfiled domains.

Item Detail
Who files The domain owner (个人 or 企业)
Where Alibaba Cloud's ICP filing console (备案系统)
What you need Real-name verified Alibaba Cloud account, hosting purchased for ≥ 3 months in a mainland region, valid Chinese ID or business licence, photo on a blue/white background (taken via the Aliyun ICP app)
How long Typically 720 business days
Result A 备案号 (ICP filing number) you must display in the site footer, e.g. 京ICP备XXXXXXX号

You do not need ICP filing if:

  • You deploy to a Hong Kong, Singapore, or other non-mainland Alibaba Cloud region (no firewall on those regions for HTTP/HTTPS).
  • The service is internal (no public domain, accessed only via VPN or private IP).

A practical pattern for many teams: deploy to Hong Kong (cn-hongkong) first to skip ICP, then migrate to a mainland region once filing is complete and latency to mainland users matters more than time-to-launch.

Network exposure

Open Design's daemon binds to 127.0.0.1:7456 and is never exposed directly. Public access must go through:

  1. A reverse proxy that terminates TLS (Nginx, Caddy, Alibaba Cloud SLB/ALB).
  2. An ICP-filed domain (for mainland regions).
  3. CORS allowlist set via OPEN_DESIGN_ALLOWED_ORIGINS (Compose / deploy/.env path) or OD_ALLOWED_ORIGINS (direct-container / Kubernetes path).

See docs/install-guide.md for the full topology and reverse-proxy guidance.

Common pitfalls

Symptom Likely cause Fix
docker pull hangs or times out No image acceleration configured; pulling from docker.io directly Configure ACR mirror in /etc/docker/daemon.json
HTTP/HTTPS to your domain is blocked Domain not ICP-filed for a mainland region Complete ICP filing, or move to a non-mainland region (HK/SG)
aliyun ecs RunInstances fails with "Real-name authentication required" Account not 实名认证 Complete real-name verification in the account console
ACK pods stuck in ImagePullBackOff from a private ACR repo Cluster lacks pull credentials for the namespace Create an aliyun-acr-credential-helper secret or use the cluster's ACR plugin
200 from /api/health but UI fails to load CORS rejecting the proxied origin, or the reverse proxy not forwarding the bearer token (Compose always generates OD_API_TOKEN; only /api/health, /api/version, /api/daemon/status skip auth) Set OPEN_DESIGN_ALLOWED_ORIGINS (Compose / deploy/.env) or OD_ALLOWED_ORIGINS (direct container / ACK) to the public URL, and add proxy_set_header Authorization "Bearer <OD_API_TOKEN>" to the Nginx / SLB upstream config
ACK Pod stuck in NotReady, readiness probe fails Daemon defaulting to OD_BIND_HOST=127.0.0.1 so the kubelet can't reach it Set OD_BIND_HOST=0.0.0.0 and OD_API_TOKEN in the Pod env (the daemon refuses non-loopback binds without a token)
SLB health check fails SLB hits 7456 but security group blocks intra-VPC Allow the SLB backend CIDR on 7456/tcp in the security group

Follow-up work

This guide is intentionally docs-only. Tracked under #1025, contributions welcome:

  • A first-party ROS template for one-click ECS provisioning.
  • An official Helm chart for ACK with multi-replica safety (external DB or shared volume topology).
  • An end-to-end one-click script equivalent to deploy/scripts/install.sh but targeting ECS via the aliyun CLI.
  • A Chinese-language sibling of this file at docs/deployment/cloud/aliyun.zh-CN.md once the English content stabilises.
  • Verification screenshots under docs/screenshots/deployment/aliyun/, mirroring the Docker layout.

References