402 lines
16 KiB
Markdown
402 lines
16 KiB
Markdown
# OpenWork Host (Docker)
|
|
|
|
## Pull-only evaluation stack
|
|
|
|
Run Den API, Den web, and MySQL from published images without cloning or building the repository:
|
|
|
|
```bash
|
|
curl -fsSLo docker-compose.eval.yml \
|
|
https://raw.githubusercontent.com/different-ai/openwork/9f8645ebc482c15ab99c0cf155aabaa411e1ca6a/packaging/docker/docker-compose.eval.yml
|
|
printf '%s %s\n' \
|
|
'69cc7f2666157b7697ebf69b31b0c83887dd99e796c7d956f9ccaab8fa8bf2fc' \
|
|
'docker-compose.eval.yml' | shasum -a 256 --check
|
|
umask 077
|
|
printf 'OPENWORK_AUTH_SECRET=%s\nOPENWORK_DB_ENCRYPTION_KEY=%s\n' \
|
|
"$(openssl rand -hex 32)" "$(openssl rand -hex 32)" > .env
|
|
docker compose -f docker-compose.eval.yml up -d --wait
|
|
```
|
|
|
|
Open `http://localhost:3005` and create an account. See the [evaluation guide](../../packages/docs/self-host/evaluate-with-docker-compose.mdx) for configuration, secure remote access, limitations, and cleanup.
|
|
|
|
This stack is for evaluation only. Its HTTP ports bind to loopback, but it still enables public signup, uses development credentials, and does not provide production workers, sandboxes, or email delivery.
|
|
|
|
## Den local stack (Docker)
|
|
|
|
One command for the Den control plane, local MySQL, and the cloud web app.
|
|
|
|
From the repo root:
|
|
|
|
```bash
|
|
./packaging/docker/den-dev-up.sh
|
|
```
|
|
|
|
Or via pnpm:
|
|
|
|
```bash
|
|
pnpm dev:den-docker
|
|
```
|
|
|
|
What it does:
|
|
- Starts **MySQL** for the Den service
|
|
- Starts **Den control plane** on port 8788 inside Docker with `PROVISIONER_MODE=stub`
|
|
- Runs **Den migrations** automatically before the API starts in the local compose stack
|
|
- Starts the **OpenWork Cloud web app** on port 3005 inside Docker
|
|
- Points the web app's auth + API proxy routes at the local Den service
|
|
- Prints randomized host URLs so multiple stacks can run side by side
|
|
|
|
Production-oriented EE images:
|
|
- `Dockerfile.den` -> `ghcr.io/different-ai/openwork-den-api`
|
|
- `Dockerfile.den-web` -> `ghcr.io/different-ai/openwork-den-web`
|
|
- `Dockerfile.inference` -> `ghcr.io/different-ai/openwork-inference`
|
|
|
|
These images are intended for Terraform, Helm, ECS, EKS, and customer-cloud deployments. Prefer immutable tags or digests in production.
|
|
|
|
Publish flow:
|
|
- Release tags like `v0.17.1` publish images tagged `v0.17.1`, `0.17.1`, `sha-<commit>`, and `latest`.
|
|
- The same workflow publishes the Helm chart to `oci://ghcr.io/different-ai/charts/openwork-ee` with chart version `0.17.1`.
|
|
- Manual publishes from a branch require `push=true` and an explicit `chart_version`.
|
|
|
|
Health and smoke expectations:
|
|
- Published service images include shallow Docker healthchecks for the HTTP process only.
|
|
- Den API and inference probe `GET /health`; Den web probes `GET /api/health`.
|
|
- The publish workflow loads each PR image locally and probes the same endpoint without cloud secrets. Production deployments should still add dependency-aware readiness checks where needed.
|
|
|
|
### Demo org seed
|
|
|
|
After the Den DB is running, seed a full local demo org with users, teams, pending invites, and imported plugin data from `anthropics/knowledge-work-plugins`:
|
|
|
|
```bash
|
|
pnpm dev:den:seed-demo
|
|
```
|
|
|
|
The seed is local/dev-only, idempotent for the `acme-robotics-demo` org, and does not create workers or live integrations. It imports plugin marketplace rows, plugin rows, access grants, and config objects so plugin pages look populated without connecting external services.
|
|
|
|
Default demo login:
|
|
|
|
- Email: `alex@acme.test`
|
|
- Password: `OpenWorkDemo123!`
|
|
|
|
For the Docker stack with randomized MySQL ports, source the printed runtime env file first and pass `DEN_MYSQL_URL` as `DATABASE_URL`:
|
|
|
|
```bash
|
|
source tmp/.den-dev-env-<id>
|
|
DATABASE_URL="$DEN_MYSQL_URL" pnpm dev:den:seed-demo
|
|
```
|
|
|
|
Set `DEN_DEMO_SEED_FETCH_GITHUB=0` to skip live GitHub source fetching and use built-in plugin fallbacks only.
|
|
|
|
Useful commands:
|
|
- Logs: `docker compose -p <project> -f packaging/docker/docker-compose.den-dev.yml logs`
|
|
- Tear down: `docker compose -p <project> -f packaging/docker/docker-compose.den-dev.yml down`
|
|
- Tear down + reset DB: `docker compose -p <project> -f packaging/docker/docker-compose.den-dev.yml down -v`
|
|
|
|
Optional env vars (via `.env` or `export`):
|
|
- `DEN_API_PORT` — host port to map to the Den control plane :8788
|
|
- `DEN_WEB_PORT` — host port to map to the cloud web app :3005
|
|
- `DEN_BETTER_AUTH_SECRET` — Better Auth secret (auto-generated if unset)
|
|
- `DEN_PUBLIC_HOST` — host name/IP used for default auth URL + printed LAN/public URLs (defaults to your machine hostname)
|
|
- `DEN_BETTER_AUTH_URL` — browser-facing auth base URL (defaults to `http://$DEN_PUBLIC_HOST:<DEN_WEB_PORT>`)
|
|
- `DEN_MCP_RESOURCE_URL` — API-facing MCP resource URL (defaults to `http://localhost:<DEN_API_PORT>/mcp`)
|
|
- `DEN_MCP_ADDITIONAL_RESOURCES` — extra public MCP resource URLs beyond the `DEN_API_PUBLIC_URL` API origin and web-app defaults
|
|
- `DEN_BETTER_AUTH_TRUSTED_ORIGINS` — trusted origins for Better Auth (defaults to `DEN_CORS_ORIGINS`)
|
|
- `DEN_CORS_ORIGINS` — trusted origins for Express CORS (defaults include hostname, localhost, `127.0.0.1`, `0.0.0.0`, and detected LAN IPv4)
|
|
- `DEN_PROVISIONER_MODE` — `stub` or `render` (defaults to `stub`)
|
|
- `DEN_WORKER_URL_TEMPLATE` — stub worker URL template with `{workerId}` placeholder
|
|
|
|
### Live OpenTelemetry Docker validation
|
|
|
|
`docker-compose.otel-lgtm.yml` adds Grafana's all-in-one development backend
|
|
(Grafana, Tempo, Loki, Prometheus, Pyroscope, and an OTLP receiver) to the Den
|
|
Docker stack. The image is pinned to `0.11.16` and its multi-arch digest.
|
|
|
|
Start only the collector/UI stack:
|
|
|
|
```bash
|
|
docker compose -f packaging/docker/docker-compose.otel-lgtm.yml up -d --wait
|
|
docker compose -f packaging/docker/docker-compose.otel-lgtm.yml ps
|
|
curl -fsS http://127.0.0.1:3000/api/health
|
|
```
|
|
|
|
Run the reproducible live E2E validator (the script resolves paths relative to
|
|
itself, so absolute-path invocations work from any working directory):
|
|
|
|
```bash
|
|
bash packaging/docker/otel-hono-live-validate.sh
|
|
```
|
|
|
|
The validator merges `docker-compose.den-dev.yml` and
|
|
`docker-compose.otel-lgtm.yml`, configures both Den services for OTLP/HTTP,
|
|
builds and starts the stack, waits for health, and makes a real request to
|
|
`/api/den/openapi.json?token=super-secret`. It then polls Grafana's Tempo,
|
|
Loki, and Prometheus datasource proxy APIs with a bounded timeout. The JSON
|
|
report proves the connected `den-web` → `den-api` trace, normalized Hono route
|
|
and request ID, secret redaction, correlated Den Web and Den API OTLP request
|
|
logs, Hono duration and active-request metric series, and absence of unhandled
|
|
runtime errors.
|
|
|
|
After the OTEL phase passes, the same validator recreates only `den` and `web`
|
|
with `DEN_OBSERVABILITY_BACKEND=none` while keeping MySQL and LGTM running. It
|
|
makes the same proxy request and records proof that the app still returns HTTP
|
|
200, request logs are structured JSON stdout for both services, and no exporter
|
|
connection/error attempt appears in the none-backend runtime logs.
|
|
|
|
Defaults use a unique Compose project and process-derived high host ports, so
|
|
parallel runs do not share state. Useful controls:
|
|
|
|
```bash
|
|
KEEP_STACK=1 bash packaging/docker/otel-hono-live-validate.sh # keep containers (left in none mode after the final phase)
|
|
OTEL_HONO_PROJECT=<previous-project> SKIP_BUILD=1 bash packaging/docker/otel-hono-live-validate.sh
|
|
OTEL_HONO_PORT_BASE=42000 bash packaging/docker/otel-hono-live-validate.sh
|
|
OTEL_HONO_REPORT_JSON=/tmp/otel-proof.json bash packaging/docker/otel-hono-live-validate.sh
|
|
```
|
|
|
|
Individual host ports remain overridable with `DEN_WEB_PORT`, `DEN_API_PORT`,
|
|
`DEN_MYSQL_PORT`, `OTEL_LGTM_GRAFANA_PORT`,
|
|
`OTEL_LGTM_OTLP_GRPC_PORT`, and `OTEL_LGTM_OTLP_HTTP_PORT`. Override
|
|
`OTEL_HONO_PROJECT`, `OTEL_HONO_POLL_SECONDS`, or
|
|
`OTEL_HONO_COMPOSE_WAIT_SECONDS` when a stable project or different bounded
|
|
timeouts are needed. The default report is
|
|
`tmp/otel-hono-live-<project>/report.json`.
|
|
|
|
Open Grafana at `http://127.0.0.1:3000` and log in with `admin` / `admin`. Use Explore with the pre-provisioned Tempo, Loki, and Prometheus data sources for traces, logs, and metrics.
|
|
|
|
To start the same merged stack manually:
|
|
|
|
```bash
|
|
DEN_OBSERVABILITY_BACKEND=otel \
|
|
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-lgtm:4318 \
|
|
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://otel-lgtm:4318/v1/traces \
|
|
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://otel-lgtm:4318/v1/metrics \
|
|
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://otel-lgtm:4318/v1/logs \
|
|
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
|
|
OTEL_TRACES_EXPORTER=otlp \
|
|
OTEL_METRICS_EXPORTER=otlp \
|
|
OTEL_LOGS_EXPORTER=otlp \
|
|
OTEL_TRACES_SAMPLER=parentbased_always_on \
|
|
docker compose -p openwork-den-otel \
|
|
-f packaging/docker/docker-compose.den-dev.yml \
|
|
-f packaging/docker/docker-compose.otel-lgtm.yml \
|
|
up --build --wait
|
|
```
|
|
|
|
Run the services outside Docker against the same local receiver:
|
|
|
|
```bash
|
|
docker compose -f packaging/docker/docker-compose.otel-lgtm.yml up -d --wait
|
|
|
|
DEN_OBSERVABILITY_BACKEND=otel \
|
|
OTEL_SERVICE_NAME=den-api \
|
|
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 \
|
|
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
|
|
pnpm dev:den:api
|
|
|
|
DEN_OBSERVABILITY_BACKEND=otel \
|
|
OTEL_SERVICE_NAME=den-web \
|
|
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 \
|
|
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
|
|
pnpm dev:den:web
|
|
```
|
|
|
|
Useful signal controls:
|
|
|
|
```bash
|
|
OTEL_TRACES_EXPORTER=none # disable traces
|
|
OTEL_METRICS_EXPORTER=none # disable metrics
|
|
OTEL_LOGS_EXPORTER=none # disable logs
|
|
OTEL_TRACES_SAMPLER=parentbased_traceidratio OTEL_TRACES_SAMPLER_ARG=0.25
|
|
```
|
|
|
|
`docker-compose.den-dev.yml` forwards the full OTLP runtime surface to both Den
|
|
services: base and per-signal endpoints, base and per-signal protocol values,
|
|
headers, per-signal exporters, and sampler settings. It also forwards runtime
|
|
Sentry settings (`SENTRY_DSN`, `SENTRY_TRACES_SAMPLE_RATE`, `SENTRY_LOG_LEVEL`,
|
|
`SENTRY_ENVIRONMENT`, `SENTRY_RELEASE`, and `SENTRY_DIST`) while keeping
|
|
distinct `OTEL_SERVICE_NAME` defaults for Den API and Den Web. Sentry tracing
|
|
defaults to `0.01`; `SENTRY_LOG_LEVEL` defaults to `warn` so successful request
|
|
logs stay on stdout unless explicitly enabled for Sentry.
|
|
|
|
Sentry source-map upload is build-time only. Runtime Compose and Helm settings
|
|
cannot upload maps after the image is built. The Dockerfiles default their
|
|
upload flags to `0`, so normal builds and PR builds skip Sentry secret reads
|
|
cleanly. To opt in for a trusted build, pass only non-secret upload flags as
|
|
build args and pass credentials as BuildKit secrets; never pass Sentry
|
|
credentials through `ARG`, `ENV`, or Compose runtime env.
|
|
|
|
For custom Den API images, `DEN_UPLOAD_SENTRY_SOURCEMAPS=1` requires
|
|
`sentry_auth_token`, `sentry_org`, `sentry_project`, and `sentry_release`.
|
|
`sentry_url` and `sentry_dist` are optional:
|
|
|
|
```bash
|
|
SENTRY_AUTH_TOKEN=... SENTRY_ORG=... SENTRY_PROJECT=... \
|
|
SENTRY_RELEASE=2026.07.11 SENTRY_DIST=den-api \
|
|
docker buildx build \
|
|
-f packaging/docker/Dockerfile.den \
|
|
--build-arg DEN_UPLOAD_SENTRY_SOURCEMAPS=1 \
|
|
--secret id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
|
|
--secret id=sentry_org,env=SENTRY_ORG \
|
|
--secret id=sentry_project,env=SENTRY_PROJECT \
|
|
--secret id=sentry_release,env=SENTRY_RELEASE \
|
|
--secret id=sentry_dist,env=SENTRY_DIST \
|
|
-t openwork-den-api:sentry .
|
|
```
|
|
|
|
For custom Den Web images, `DEN_WEB_UPLOAD_SENTRY_SOURCEMAPS=1` requires
|
|
`sentry_auth_token`, `sentry_org`, and `sentry_project`. `sentry_url`,
|
|
`sentry_release`, and `sentry_dist` are optional. The Dockerfile exports the
|
|
upload flag and secrets only inside the build step; runtime Sentry still
|
|
requires its own runtime environment configuration:
|
|
|
|
```bash
|
|
SENTRY_AUTH_TOKEN=... SENTRY_ORG=... SENTRY_PROJECT=... \
|
|
SENTRY_RELEASE=2026.07.11 SENTRY_DIST=den-web SENTRY_URL=... \
|
|
docker buildx build \
|
|
-f packaging/docker/Dockerfile.den-web \
|
|
--build-arg DEN_WEB_UPLOAD_SENTRY_SOURCEMAPS=1 \
|
|
--secret id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
|
|
--secret id=sentry_org,env=SENTRY_ORG \
|
|
--secret id=sentry_project,env=SENTRY_PROJECT \
|
|
--secret id=sentry_url,env=SENTRY_URL \
|
|
--secret id=sentry_release,env=SENTRY_RELEASE \
|
|
--secret id=sentry_dist,env=SENTRY_DIST \
|
|
-t openwork-den-web:sentry .
|
|
```
|
|
|
|
The EE image publish workflow never passes repository Sentry secrets to
|
|
`pull_request` builds. On trusted publish events, it checks for the complete
|
|
configuration for each image and uses the Sentry upload build only for that
|
|
configured image; otherwise it publishes the normal image without failing the
|
|
release. Generic prebuilt images cannot perform source-map uploads for a
|
|
deployment-specific Sentry project after publication.
|
|
|
|
Caveats: `grafana/otel-lgtm` is intended for development, demos, and tests, not production retention or security. The default Grafana credentials are public, and the digest should be refreshed deliberately when updating the image tag. Upstream images are signed with cosign; see the `grafana/docker-otel-lgtm` README if you need signature verification in CI.
|
|
|
|
### Install links (self-host)
|
|
|
|
Run the install-link migration once against the Den database:
|
|
|
|
```bash
|
|
docker compose -f packaging/docker/docker-compose.den-dev.yml exec den sh -lc "node /app/ee/packages/den-db/dist/scripts/bootstrap.js"
|
|
```
|
|
|
|
Set `DEN_BOOTSTRAP_ADMIN_EMAILS` on the Den API service, restart it, open `/admin`, and toggle `Install links` for each org. Optional installer artifact env vars are `OPENWORK_INSTALLER_RELEASE_TAG`, `OPENWORK_INSTALLER_RELEASE_REPO`, and `OPENWORK_INSTALLER_ARTIFACTS_DIR`; see the [operator guide](../../docs/org-install-links.md).
|
|
|
|
### Faster inner-loop alternative
|
|
|
|
If you are iterating on Den locally and do not need the full Dockerized web stack, use the hybrid path instead:
|
|
|
|
From the OpenWork repo root:
|
|
|
|
```bash
|
|
pnpm dev:den
|
|
```
|
|
|
|
Or from the OpenWork enterprise root:
|
|
|
|
```bash
|
|
pnpm --dir _repos/openwork dev:den
|
|
```
|
|
|
|
What it does:
|
|
- Starts only **MySQL** in Docker
|
|
- Runs **Den controller** locally in watch mode
|
|
- Runs **OpenWork Cloud web app** locally in Next.js dev mode
|
|
- Reuses the existing local-dev wiring in `scripts/dev-web-local.sh`
|
|
|
|
This is usually the fastest path for UI/auth/control-plane iteration because it avoids rebuilding the Docker web image on each boot.
|
|
|
|
If you want to run the pieces in separate terminals, use the root package scripts:
|
|
|
|
```bash
|
|
pnpm dev:den:mysql
|
|
pnpm dev:den:db-push
|
|
pnpm dev:den:api
|
|
pnpm dev:den:web
|
|
```
|
|
|
|
The split API/web flow defaults to Den API on `http://localhost:8790` and Den web on `http://localhost:3005`. Stop the local MySQL container with:
|
|
|
|
```bash
|
|
pnpm dev:den:mysql:down
|
|
```
|
|
|
|
---
|
|
|
|
## Pre-baked Micro-Sandbox Image
|
|
|
|
For micro-sandbox work, use the pre-baked image that compiles `openwork-server` from source and downloads the pinned `opencode` binary during `docker build`.
|
|
|
|
Build it from the repo root:
|
|
|
|
```bash
|
|
./scripts/build-microsandbox-openwork-image.sh
|
|
```
|
|
|
|
Run it locally:
|
|
|
|
```bash
|
|
docker run --rm -p 8787:8787 \
|
|
-e OPENWORK_CONNECT_HOST=127.0.0.1 \
|
|
openwork-microsandbox:dev
|
|
```
|
|
|
|
Defaults:
|
|
- `OPENWORK_TOKEN=microsandbox-token`
|
|
- `OPENWORK_HOST_TOKEN=microsandbox-host-token`
|
|
- `OPENWORK_APPROVAL_MODE=auto`
|
|
|
|
Verification:
|
|
- Health: `curl http://127.0.0.1:8787/health`
|
|
- Authenticated API call: `curl -H "Authorization: Bearer microsandbox-token" http://127.0.0.1:8787/workspaces`
|
|
- Docker health: `docker inspect --format '{{json .State.Health}}' <container>`
|
|
|
|
Useful overrides:
|
|
- `OPENWORK_TOKEN` — set your own client bearer token
|
|
- `OPENWORK_HOST_TOKEN` — set your own host/admin token
|
|
- `OPENWORK_CONNECT_HOST` — host name embedded in the printed connect URL
|
|
- `DOCKER_PLATFORM` — optional platform passed to `docker build`
|
|
|
|
---
|
|
|
|
## Production container
|
|
|
|
This is a minimal packaging template to run the OpenWork Host contract in a single container.
|
|
|
|
It runs:
|
|
|
|
- `openwork-server` published on `0.0.0.0:8787` (the only published surface)
|
|
- Managed `opencode` launched internally by `openwork-server`
|
|
|
|
### Local run (compose)
|
|
|
|
From this directory:
|
|
|
|
```bash
|
|
docker compose up --build
|
|
```
|
|
|
|
Then open:
|
|
|
|
- `http://127.0.0.1:8787/health`
|
|
|
|
### Config
|
|
|
|
Recommended env vars:
|
|
|
|
- `OPENWORK_TOKEN` (client token)
|
|
- `OPENWORK_HOST_TOKEN` (host/owner token)
|
|
|
|
Optional:
|
|
|
|
- `OPENWORK_APPROVAL_MODE=auto|manual`
|
|
- `OPENWORK_APPROVAL_TIMEOUT_MS=30000`
|
|
|
|
Persistence:
|
|
|
|
- Workspace is mounted at `/workspace`
|
|
- Host data dir is mounted at `/data` (OpenCode caches + OpenWork server config/tokens)
|
|
|
|
### Notes
|
|
|
|
- OpenCode is not exposed directly; access it via the OpenWork proxy (`/opencode/*`).
|
|
- For PaaS, replace `./workspace:/workspace` with a volume or a checkout strategy (git clone on boot).
|