mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-20 06:15:06 +08:00
284 lines
11 KiB
Markdown
284 lines
11 KiB
Markdown
# Docker deployment
|
|
|
|
This deployment ships OpenDesign as a single Alpine-based runtime image. The
|
|
daemon serves both the API and the built Next.js static export, so there is no
|
|
separate nginx container.
|
|
|
|
## Local compose
|
|
|
|
Before starting:
|
|
|
|
1. Copy the environment template:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
2. Generate a secure token (recommended unless your reverse proxy will both authenticate every request and set `OPEN_DESIGN_DISABLE_API_AUTH=1`):
|
|
|
|
```bash
|
|
openssl rand -hex 32
|
|
```
|
|
|
|
3. Open `.env` in your editor and choose one auth mode:
|
|
- default: paste the token into `OD_API_TOKEN=`
|
|
- trusted reverse proxy that already authenticates every request: leave `OD_API_TOKEN=` empty and set `OPEN_DESIGN_DISABLE_API_AUTH=1`
|
|
|
|
Then pull and start the service:
|
|
|
|
```bash
|
|
OPEN_DESIGN_IMAGE=ghcr.io/nexu-io/od:latest docker compose pull
|
|
OPEN_DESIGN_IMAGE=ghcr.io/nexu-io/od:latest docker compose up -d --no-build
|
|
```
|
|
|
|
Use `ghcr.io/nexu-io/od:latest` for the latest stable image, or
|
|
`ghcr.io/nexu-io/od:<version>` to pin a supported release.
|
|
|
|
Open `http://127.0.0.1:7456`. When Docker's bridge makes the browser appear as a
|
|
non-loopback peer, the browser displays its native sign-in dialog. Enter
|
|
`open-design` as the username and the `OD_API_TOKEN` value from `.env` as the
|
|
password. The browser reuses those credentials for same-origin API requests;
|
|
CLI clients and reverse proxies can continue to send
|
|
`Authorization: Bearer <OD_API_TOKEN>`.
|
|
|
|
The published `ghcr.io/nexu-io/od` package must be public for anonymous
|
|
`docker pull`, Docker Compose, and Dokploy installs to work. If GHCR returns an
|
|
authentication or access-denied error for this image, an organization maintainer
|
|
must open GitHub -> Packages -> `od` -> Package settings and change visibility
|
|
to Public. GitHub treats that as a one-way visibility change for container
|
|
packages, so confirm the package is intended to stay public before switching it.
|
|
|
|
Defaults:
|
|
|
|
- Host port: `127.0.0.1:7456` (`OPEN_DESIGN_PORT=8080` to publish on `127.0.0.1:8080`)
|
|
- Runtime data: before documenting, changing, or choosing persistent daemon
|
|
storage, you MUST read root [`AGENTS.md`](../AGENTS.md) → **Daemon data
|
|
directory contract**. This README MUST NOT restate it.
|
|
- Node heap cap: `--max-old-space-size=192`
|
|
- Compose memory cap: `384m` (`OPEN_DESIGN_MEM_LIMIT=256m` to override)
|
|
|
|
Do not publish the daemon directly on a public or shared LAN interface. The shared
|
|
API token is single-tenant authentication, not user-level access control, and both
|
|
Basic and Bearer credentials require TLS outside localhost. Remote deployments
|
|
should keep Compose bound to localhost and put an authenticated TLS reverse proxy,
|
|
SSH tunnel, or VPN in front of it.
|
|
|
|
When exposing the service through an authenticated public IP, domain, or reverse
|
|
proxy, set `OPEN_DESIGN_ALLOWED_ORIGINS` to the exact browser origins that should
|
|
be allowed to call `/api`:
|
|
|
|
```bash
|
|
OPEN_DESIGN_ALLOWED_ORIGINS=https://od.example.com,http://203.0.113.10:7456 docker compose up -d --no-build
|
|
```
|
|
|
|
If the reverse proxy already authenticates every request and you do not want it
|
|
to inject `Authorization: Bearer <OD_API_TOKEN>` upstream, set:
|
|
|
|
```bash
|
|
OPEN_DESIGN_DISABLE_API_AUTH=1
|
|
```
|
|
|
|
Use this only for trusted deployments where the daemon is reachable strictly
|
|
through that authenticated proxy. It disables daemon-side bearer enforcement for
|
|
all `/api/*` requests, so direct access to the daemon must remain blocked. The
|
|
Compose variable maps to daemon env `OD_DISABLE_API_AUTH`.
|
|
|
|
Pin a specific published image with a digest instead of the mutable `latest` tag:
|
|
|
|
```bash
|
|
OPEN_DESIGN_IMAGE=ghcr.io/nexu-io/od@sha256:<digest> docker compose up -d --no-build
|
|
```
|
|
The image intentionally does not bundle Claude/Codex/Gemini CLI binaries. Keep
|
|
those outside the image, or build a separate private runtime layer if a server
|
|
deployment needs local code-agent CLIs installed in the container.
|
|
|
|
## Linux: mounting host agent CLIs
|
|
|
|
On Linux you can mount host-installed agent CLIs (Claude Code, opencode, Codex,
|
|
…) into the container without rebuilding the image. The override file
|
|
`docker-compose.linux.yml` is already loaded automatically by `install.sh` on
|
|
Linux; it switches to `network_mode: host` and adds the CLI mounts.
|
|
|
|
**1. Build the local image** (adds `libc6-compat` so glibc-linked CLIs run on Alpine):
|
|
|
|
```bash
|
|
docker build -t open-design-local -f deploy/Dockerfile.local .
|
|
```
|
|
|
|
**2. Point `.env` at the local image:**
|
|
|
|
```bash
|
|
OPEN_DESIGN_IMAGE=open-design-local
|
|
```
|
|
|
|
**3. Edit `docker-compose.linux.yml`** to match your CLI install paths, then start:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.yml -f docker-compose.linux.yml up -d --no-build
|
|
```
|
|
|
|
Common install paths:
|
|
|
|
| CLI | Install / default path |
|
|
|-----|------------------------|
|
|
| Claude Code | `~/.local/bin/claude` (symlink) + `~/.local/share/claude` (binaries) |
|
|
| opencode | `~/.opencode/bin/opencode` |
|
|
| Codex | `~/.local/bin/codex` |
|
|
| Vela / AMR | npm package `@powerformer/vela-cli`; [Open Design AMR](https://open-design.ai/amr) is the browser account/wallet page |
|
|
|
|
Vela is published as the `@powerformer/vela-cli` npm package. The Open Design
|
|
AMR URL above is not a shell installer. For Linux Docker, install Vela under a
|
|
dedicated prefix so its launcher and platform package can be mounted together:
|
|
|
|
```bash
|
|
VELA_PREFIX="$HOME/.local/share/vela"
|
|
npm install --global --prefix "$VELA_PREFIX" @powerformer/vela-cli
|
|
export PATH="$VELA_PREFIX/bin:$PATH"
|
|
which vela
|
|
vela --version
|
|
```
|
|
|
|
Mount that complete prefix into the container, then expose its `bin` directory
|
|
through `PATH` (or set `VELA_BIN` to the container-visible launcher):
|
|
|
|
```yaml
|
|
environment:
|
|
PATH: /mnt/host-vela/bin:/mnt/host-local-bin:/mnt/host-opencode:/usr/local/bin:/usr/bin:/bin
|
|
VELA_BIN: /mnt/host-vela/bin/vela
|
|
volumes:
|
|
- ${HOME}/.local/share/vela:/mnt/host-vela:ro
|
|
```
|
|
|
|
Once the container is running, inspect both `PATH` discovery and any explicit
|
|
Vela override:
|
|
|
|
```bash
|
|
docker compose exec open-design sh -lc 'which vela; echo "$VELA_BIN"'
|
|
```
|
|
|
|
Use either a `vela` launcher discoverable on `PATH` or a `VELA_BIN` path that
|
|
exists inside the container. `VELA_BIN` is optional when `vela` is discoverable
|
|
on `PATH`.
|
|
|
|
The daemon auto-detects any CLI that is visible in `PATH` at startup — no extra
|
|
configuration needed. For another CLI installed in a non-standard path, mount
|
|
its complete install prefix read-only and prepend its binary directory to
|
|
`PATH` in `docker-compose.linux.yml`, then restart. When a launcher is a symlink,
|
|
mounting only the directory that contains the symlink may omit its target.
|
|
|
|
```bash
|
|
docker compose -f docker-compose.yml -f docker-compose.linux.yml up -d --no-build
|
|
```
|
|
|
|
If a CLI fails with `symbol not found` or relocation errors despite `libc6-compat`,
|
|
the compose file mounts `/lib/x86_64-linux-gnu` and `/lib64` from the host read-only,
|
|
which provides full glibc for the most demanding binaries. These paths are **amd64-only**;
|
|
on arm64 replace them with `/lib/aarch64-linux-gnu:/lib/aarch64-linux-gnu:ro`.
|
|
|
|
**Upgrading from a previous install:** if you previously ran the container as a different
|
|
user (e.g. `node`, uid 1000), the data volume may need an ownership fix before the daemon
|
|
can write to it:
|
|
|
|
```bash
|
|
docker run --rm -v open-design_open_design_data:/data alpine chown -R 1001:1001 /data
|
|
```
|
|
|
|
Pass provider API keys via `.env`:
|
|
|
|
```bash
|
|
DEEPSEEK_API_KEY=sk-…
|
|
ANTHROPIC_API_KEY=sk-ant-…
|
|
OPENAI_API_KEY=sk-…
|
|
```
|
|
|
|
If you install Codex inside an unprivileged Linux container and it fails while
|
|
creating its `workspace-write` sandbox, opt into Codex's full-access mode for
|
|
all Codex runs in that deployment:
|
|
|
|
```bash
|
|
OD_CODEX_SANDBOX=danger-full-access docker compose up -d --no-build
|
|
```
|
|
|
|
Only the exact value `danger-full-access` is supported; unknown values are
|
|
ignored. Use this only for trusted, single-user deployments. It lets Codex run
|
|
without the workspace-write sandbox, which is useful when the container host
|
|
blocks unprivileged user namespaces, but it gives the Codex process broader
|
|
filesystem access inside the container.
|
|
|
|
## Manual image publish override
|
|
|
|
```bash
|
|
deploy/scripts/publish-images.sh --image_tag latest
|
|
```
|
|
|
|
Useful overrides:
|
|
|
|
```bash
|
|
IMAGE_NAMESPACE=your-ghcr-org deploy/scripts/publish-images.sh --arch arm64
|
|
deploy/scripts/publish-images.sh --image ghcr.io/your-org/od:0.1.0
|
|
```
|
|
|
|
The script defaults to:
|
|
|
|
- `ghcr.io/nexu-io/od:<tag>`
|
|
- `linux/amd64,linux/arm64`
|
|
- `skopeo` push strategy with registry credentials read from `~/.docker/config.json`
|
|
- preloading base images through `skopeo` to reduce Docker Hub pull flakiness
|
|
|
|
If `127.0.0.1:7890` is available and no proxy is already set, the script uses it
|
|
for registry access and passes `host.docker.internal:7890` into Docker builds. The
|
|
host-gateway alias is only added for builds that need this local proxy mapping.
|
|
|
|
### Colima swap helper for Apple Silicon
|
|
|
|
`deploy/scripts/prepare-colima-build-swap.sh` is for manual Docker image
|
|
publishing from an Apple Silicon macOS host that uses Colima as the Docker VM.
|
|
The helper is intentionally Apple Silicon-only because the failure mode it covers
|
|
is local arm64 Colima builds exhausting a small Linux VM while preparing
|
|
multi-arch images. It exits before touching Colima on non-macOS or
|
|
non-Apple-Silicon hosts.
|
|
|
|
Low-memory Colima VMs can run out of RAM during multi-arch image builds. The
|
|
helper checks the VM memory and swap status, then creates and enables a temporary
|
|
swap file only when the VM has no swap and less than 4 GiB of RAM. The 4 GiB
|
|
threshold is a conservative default for short-lived manual publishes on small
|
|
Colima profiles; raise `COLIMA_BUILD_SWAP_MEMORY_THRESHOLD_KIB` if larger builds
|
|
still OOM, or lower it if you only want swap for very small VMs.
|
|
|
|
Prefer increasing the Colima VM memory (`colima start --memory <GiB>` or the
|
|
profile config) when you want a persistent build machine. Use this helper when
|
|
you need a temporary, reversible boost for one manual publish without resizing
|
|
or recreating the VM.
|
|
|
|
Run it before a manual publish if Docker builds fail with out-of-memory errors,
|
|
or if `status` shows a small Colima VM with no swap. The swap remains active
|
|
until cleanup or VM restart, so use a shell trap for one-off sessions:
|
|
|
|
```bash
|
|
deploy/scripts/prepare-colima-build-swap.sh status
|
|
deploy/scripts/prepare-colima-build-swap.sh
|
|
trap 'deploy/scripts/prepare-colima-build-swap.sh cleanup' EXIT
|
|
deploy/scripts/publish-images.sh --image_tag latest
|
|
```
|
|
|
|
Useful overrides:
|
|
|
|
```bash
|
|
COLIMA_BUILD_SWAP_SIZE=6G deploy/scripts/prepare-colima-build-swap.sh
|
|
COLIMA_BUILD_SWAP_MEMORY_THRESHOLD_KIB=6291456 deploy/scripts/prepare-colima-build-swap.sh
|
|
COLIMA_BIN=/opt/homebrew/bin/colima deploy/scripts/prepare-colima-build-swap.sh status
|
|
COLIMA_BUILD_SWAP_CLEANUP_FORCE=1 COLIMA_BUILD_SWAPFILE=/custom-swapfile deploy/scripts/prepare-colima-build-swap.sh cleanup
|
|
```
|
|
|
|
`cleanup` removes the default helper path and the old helper path. If you set a
|
|
custom `COLIMA_BUILD_SWAPFILE`, cleanup refuses to remove it unless
|
|
`COLIMA_BUILD_SWAP_CLEANUP_FORCE=1` is also set.
|
|
|
|
### Docker Desktop on macOS
|
|
|
|
Docker Desktop bridge networking makes host-browser traffic appear to the daemon
|
|
as a non-loopback peer. This is expected: keep the default bridge configuration
|
|
and complete the browser's native sign-in prompt with username `open-design` and
|
|
the `OD_API_TOKEN` value from `.env`. Host networking is no longer required for
|
|
the web UI authentication path.
|