feat(deploy): docker-compose + Helm chart entry slice (Phase 5)

Plan J4 / spec §15.4 / §15.5 / §16 Phase 5.

Three landings:

- deploy/Dockerfile now COPYs plugins/_official/ into the image so
  the bundled atom plugins from §3.I3 register on container boot —
  without this, registerBundledPlugins() silently no-ops inside the
  container and the §23 self-bootstrap promise breaks for hosted
  deployments.

- tools/pack/docker-compose.yml ships the canonical hosted-mode
  manifest spec §15.4 calls out: two-volume layout (od-data +
  od-config) per §15.2, OD_BIND_HOST=0.0.0.0 + OD_API_TOKEN +
  OD_NAMESPACE + snapshot retention knobs as env, /api/daemon/status
  as the healthcheck endpoint (Phase 1.5). Drop-in usable with
  `docker compose -f tools/pack/docker-compose.yml up -d`.

- tools/pack/helm/open-design/{Chart.yaml,values.yaml,README.md}
  pins the Helm chart parameter surface for the per-cloud overrides
  spec §15.5 enumerates (AWS / GCP / Azure / Aliyun / Tencent /
  Huawei / self-hosted). Templates land in the Phase 5 follow-up
  PR; the values schema is locked here so the per-cloud override
  files (values-<cloud>.yaml) review in isolation.

scripts/guard.ts allowlist gains
`packages/agui-adapter/esbuild.config.mjs` so the new package
passes the residual-JS guard.

Daemon tests stay at 1486/1486 (deploy artifacts only).

Co-authored-by: Tom Huang <1043269994@qq.com>
This commit is contained in:
Cursor Agent
2026-05-09 13:20:53 +00:00
co-authored by Tom Huang
parent 847304ebc5
commit bf30b308e3
6 changed files with 227 additions and 0 deletions
+6
View File
@@ -81,6 +81,12 @@ COPY --chown=open-design:open-design craft ./craft
COPY --chown=open-design:open-design prompt-templates ./prompt-templates
COPY --chown=open-design:open-design assets/frames ./assets/frames
COPY --chown=open-design:open-design assets/community-pets ./assets/community-pets
# Plan §3.J4 / spec §23.3.5 — bundled atom plugins registered on
# daemon boot. The directory contains `plugins/_official/atoms/<atom>/`
# pairs (SKILL.md + open-design.json); registerBundledPlugins() walks
# it on startup so the container ships with first-party atoms reachable
# via the same registry path third-party plugins use.
COPY --chown=open-design:open-design plugins/_official ./plugins/_official
RUN mkdir -p /app/.od && \
chown -R open-design:open-design /app
+1
View File
@@ -36,6 +36,7 @@ const residualSkippedDirectories = new Set([
const residualAllowedExactPaths = new Set([
// esbuild config entrypoints are executed directly by Node before package
// dist output exists.
"packages/agui-adapter/esbuild.config.mjs",
"packages/contracts/esbuild.config.mjs",
"packages/platform/esbuild.config.mjs",
"packages/plugin-runtime/esbuild.config.mjs",
+73
View File
@@ -0,0 +1,73 @@
# Phase 5 / spec §15.4 — reference Docker Compose manifest.
#
# Brings up the Open Design daemon container alongside the persistent
# volumes and the env vars hosted deployments need. Drop this file into
# any directory and run `docker compose up -d` — the only required
# customisation is the OD_API_TOKEN secret and the provider keys.
#
# Build the image first (from the repo root):
#
# docker build -f deploy/Dockerfile -t open-design/od:dev .
#
# Or pull a published tag:
#
# docker pull ghcr.io/open-design/od:edge
#
# Then `docker compose -f tools/pack/docker-compose.yml up -d`.
version: "3.9"
services:
od:
# The image tag the operator wants. Spec §15.5 expects multi-arch
# ghcr.io/open-design/od:<tag>; the build path above produces
# `open-design/od:dev` locally.
image: ${OD_IMAGE:-ghcr.io/open-design/od:edge}
container_name: od
# Spec §15.3 — every dimension the daemon respects in headless
# mode. OD_BIND_HOST=0.0.0.0 + OD_API_TOKEN is the canonical
# hosted-mode tuple; remove these two if running on a private
# docker network with an upstream reverse proxy that already
# gates access.
environment:
OD_BIND_HOST: ${OD_BIND_HOST:-0.0.0.0}
OD_PORT: ${OD_PORT:-7456}
OD_DATA_DIR: /data/od
OD_MEDIA_CONFIG_DIR: /data/config
# Set to a generated random hex (`openssl rand -hex 32`) before
# exposing to the public internet. Phase 5 wires the daemon to
# refuse OD_BIND_HOST=0.0.0.0 without OD_API_TOKEN.
OD_API_TOKEN: ${OD_API_TOKEN:-}
OD_NAMESPACE: ${OD_NAMESPACE:-production}
# Snapshot retention knobs (spec §11.4 PB2). Default ttl is 30
# days; set to 0 to keep snapshots forever.
OD_SNAPSHOT_UNREFERENCED_TTL_DAYS: ${OD_SNAPSHOT_UNREFERENCED_TTL_DAYS:-30}
OD_SNAPSHOT_GC_INTERVAL_MS: ${OD_SNAPSHOT_GC_INTERVAL_MS:-21600000}
# Provider credentials. Leave blank to require a UI / CLI
# configure step inside the running container.
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
TAVILY_API_KEY: ${TAVILY_API_KEY:-}
# Two-volume layout per spec §15.2 — mounting /data/config
# separately lets the operator rotate provider credentials on a
# different lifecycle than the project data.
volumes:
- od-data:/data/od
- od-config:/data/config
ports:
- "${OD_PORT:-7456}:${OD_PORT:-7456}"
restart: unless-stopped
healthcheck:
# /api/daemon/status is the canonical lifecycle probe (Phase 1.5).
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:${OD_PORT:-7456}/api/daemon/status >/dev/null 2>&1 || exit 1"]
interval: 30s
timeout: 5s
retries: 3
volumes:
od-data: {}
od-config: {}
+23
View File
@@ -0,0 +1,23 @@
apiVersion: v2
name: open-design
description: |
Open Design daemon Helm chart. Spec §15.5 reference deployment for
AWS / GCP / Azure / Aliyun / Tencent / Huawei / self-hosted Kubernetes.
Pairs with `tools/pack/docker-compose.yml` as the docker-only entry
point.
type: application
version: 0.1.0
appVersion: "0.5.0"
icon: https://open-design.ai/favicon.png
keywords:
- open-design
- design-agent
- generative-ui
- plugins
- skills
home: https://open-design.ai
sources:
- https://github.com/nexu-io/open-design
maintainers:
- name: Open Design
url: https://open-design.ai
+39
View File
@@ -0,0 +1,39 @@
# Open Design Helm chart
> Spec §15.5 reference deployment.
> Status: **values surface locked, templates pending.**
This chart is the parameter contract Phase 5 standardised on. The
`templates/` directory is intentionally absent in this slice — the
follow-up PR that lands the Kubernetes manifests will consume the
same `values.yaml` schema.
## Why a placeholder
The plan §6 Phase 5 deliverable is a **single** Helm chart with values
presets per cloud. Splitting that into "values surface now, templates
later" keeps the cloud-specific overrides reviewable in isolation
(every cloud's volume + secret pattern shows up as a `values-<cloud>.yaml`
override file).
## Cloud overrides (planned values files)
```text
values-aws.yaml persistence.backend=efs secrets.backend=secrets-manager
values-gcp.yaml persistence.backend=filestore secrets.backend=secret-manager
values-azure.yaml persistence.backend=files secrets.backend=key-vault
values-aliyun.yaml persistence.backend=nas secrets.backend=kms
values-tencent.yaml persistence.backend=cfs secrets.backend=ssm
values-huawei.yaml persistence.backend=sfs secrets.backend=kms
values-self.yaml persistence.backend=hostPath secrets.backend=env
```
## Installing once the templates land
```bash
helm repo add open-design https://open-design.ai/charts
helm install od open-design/open-design \
--set image.tag=edge \
--set secrets.apiToken="$(openssl rand -hex 32)" \
-f values-aws.yaml # one of the cloud overrides above
```
+85
View File
@@ -0,0 +1,85 @@
# Open Design daemon — Helm chart values. Spec §15.5.
#
# Override the image / persistence / secret pattern per cloud:
# AWS: persistence.backend=efs secrets.backend=secrets-manager
# GCP: persistence.backend=filestore + gcsfuse secrets.backend=secret-manager
# Azure: persistence.backend=files secrets.backend=key-vault
# Aliyun: persistence.backend=nas secrets.backend=kms
# Tencent: persistence.backend=cfs secrets.backend=ssm
# Huawei: persistence.backend=sfs secrets.backend=kms
# Self-hosted: persistence.backend=hostPath / nfs / longhorn
#
# These values are reference defaults; the chart's templates/ aren't
# wired here yet (Phase 5 follow-up). The file pins the parameter
# surface so a downstream PR can implement the templates without
# revisiting the deployment shape.
image:
repository: ghcr.io/open-design/od
tag: edge
pullPolicy: IfNotPresent
# `replicaCount: 1` is the v1 default — pluggable storage + Postgres
# adapter (spec §15.6) lift this in a follow-up.
replicaCount: 1
service:
type: ClusterIP
port: 7456
persistence:
enabled: true
data:
size: 20Gi
storageClass: ""
config:
size: 1Gi
storageClass: ""
env:
OD_BIND_HOST: 0.0.0.0
OD_PORT: "7456"
OD_DATA_DIR: /data/od
OD_MEDIA_CONFIG_DIR: /data/config
OD_NAMESPACE: production
OD_SNAPSHOT_UNREFERENCED_TTL_DAYS: "30"
OD_SNAPSHOT_GC_INTERVAL_MS: "21600000"
# Secrets are NOT inlined in values.yaml — operators inject them
# through their cloud's secret manager. This block is the schema the
# templates will consume.
secrets:
apiToken: "" # OD_API_TOKEN
anthropicApiKey: "" # ANTHROPIC_API_KEY
tavilyApiKey: "" # TAVILY_API_KEY
resources:
limits:
cpu: 2
memory: 2Gi
requests:
cpu: 250m
memory: 512Mi
podSecurityContext:
runAsUser: 1001
runAsGroup: 1001
fsGroup: 1001
runAsNonRoot: true
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: false
ingress:
enabled: false
className: ""
annotations: {}
hosts: []
tls: []
nodeSelector: {}
tolerations: []
affinity: {}