Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 15 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,19 @@ cd <example> && rig deploy
| [`webhook-receiver/`](./webhook-receiver/) | Python · Flask | `secrets:` + a server-generated `credentials:` + a `select` param for the signing algorithm |
| [`scheduled-digest/`](./scheduled-digest/) | TypeScript | a background worker loop + `/healthz` + a `number` param for the schedule |
| [`url-shortener/`](./url-shortener/) | Python · Django | the **full validated param set** (url/string/number/boolean/select/email/secret/textarea) + SQLite migrations |
| [`markdown-notes/`](./markdown-notes/) | Python · Flask | **workspace volume-backed SQLite persistence** + Markdown rendering |
| [`markdown-notes/`](./markdown-notes/) | Python · Flask | **persistent app data** + Markdown rendering |

Every example deploys with the same command — `rig deploy`. All of them rsync
code and describe their environment with `install:`. Several also set
`reproducible: true`, which makes `rig deploy` **freeze the result of `install:`
into an image** and boot from it (see **Reproducible deploys & the hybrid model**
below). No Dockerfile anywhere — the flag is the signal.
Every example deploys with `rig deploy`. The manifest's explicit `workspace.deployment.strategy` selects **incremental app releases** or the advanced **image replacement** path. Incremental releases preserve the workspace and SSH sessions; only affected services restart.

### Established products, run reproducibly
### Established products

These run real, recognizable self-hosted products on Rigbox via a
`reproducible: true` `install:` script that installs the product on top of the
Rigbox base. `rig deploy` runs the script once in a builder VM, freezes the
result as an image, then reuses it. (Upstream Docker images like `postgres:16`
can't be booted — they lack the rigbox agent + init — so each installs the
product on the rigbox base instead.)
n8n uses release-local dependencies. Code-server and Gitea retain explicit image mode until their system-level installers are migrated. Review the strategy before connecting an existing development workspace.

| Example | Product | What it shows |
|---|---|---|
| [`code-server/`](./code-server/) | **code-server** (VS Code) | run an established product via a reproducible `install:`; settings/extensions persist under `$DATA_DIR` |
| [`gitea/`](./gitea/) | **Gitea** (Git hosting) | a headless single-binary service (install wizard locked) with SQLite + repos under `$DATA_DIR` |
| [`n8n/`](./n8n/) | **n8n** (workflow automation) | freeze a heavy `npm install` into the image; workflows persist under `$DATA_DIR` |
| [`n8n/`](./n8n/) | **n8n** (workflow automation) | install pinned dependencies into app releases; workflows persist under `$RIGBOX_APP_DATA_DIR` |

## Catalog apps

Expand Down Expand Up @@ -79,44 +70,20 @@ The point of the suite is to model the *right* primitive for each job:
- **Validated config** is a `param` with a fixed option set (`type: select`), not a
free-form env var — the server validates it and it's live-editable with
`rig app param set <key>=<value>`. Fixed infra (paths, base URLs) stays in `env:`.
- **Persistence** uses a `workspace.volumes` declaration plus explicit app
`volumes: [data]` opt-in. Apps write durable data under
`DATA_DIR=/home/developer/data`, so SQLite DBs and files survive every redeploy
and bluegreen cut-over.
- **Persistence** for incremental releases uses the managed `RIGBOX_APP_DATA_DIR` outside release directories. Image examples use their declared data paths and volume contract. Neither mode promises database rollback or automatic cross-workspace data transfer.
- **Visibility** is declared in `rig.yaml` (`visibility: public` / `private` /
`{ emails: [...] }`) so a redeploy keeps it — only an app's front door is public;
siblings reach private apps over loopback via `dependsOn`.

## Reproducible deploys & the hybrid model

Every example installs its runtime with `install:`. By default that script runs
on the workspace VM on each deploy. The established products —
**`code-server`**, **`gitea`**, **`n8n`**, and every [`catalog-apps/`](./catalog-apps/)
example — add one line, `reproducible: true`, which makes the same `install:`
**freeze into an image** instead. The command is the same — `rig deploy`:

- the **first** deploy boots a throwaway builder VM from the `base` image, runs
`install:` inside it, snapshots the rootfs as a content-addressed image, boots
the workspace from that frozen image, and rsyncs the code;
- **later** deploys reuse the cached image when the build inputs (`install:`
script, base image, lockfiles) are unchanged and **only rsync the changed
code** — no rebuild, no re-install.

That's the hybrid: build the slow, stable environment once; ride fast-changing
code over it with rsync. `install:` runs as `developer` (with passwordless
`sudo`) in an **empty** deploy dir inside the builder — so it must be
self-contained (inline any config it needs via heredocs) and idempotent, since
the exact same script runs on the workspace VM when `reproducible` is off.
Runtime wrappers (`start.sh`) still rsync in with the code: `start: bash start.sh`.

> **Builder sizing.** The builder VM boots with 1GB RAM / 1 vCPU and inherits
> the app's `workspace.resources.diskSizeMb` (3GB default, 16GB ceiling), so a
> heavy install just needs that value set high enough to hold it. The heavier
> examples (`n8n`, `firecrawl`, `open-webui`, `hermes-agent`, `excalidraw`) size
> themselves in `rig.yaml` and note their footprint in their README.

See [`design/CONTRACT.md`](./design/CONTRACT.md) → *Reproducible builds* for the
full rules and when to pick which.
## Deployment strategies

**Incremental:** quickstart, ai-chat, todo-app, webhook-receiver, scheduled-digest, url-shortener, markdown-notes, n8n, multi-app, mixed-app, and catalog Codex use managed app releases. Source, dependencies, and service configuration are staged separately from development files. Activation briefly restarts services on the same ports. Retained releases support app rollback; persistent data and database migrations are not rolled back.

**Image:** bluegreen-blog, code-server, Gitea, and the remaining catalog examples explicitly retain `workspace.deployment.strategy: image`. They install OS packages or write global tool configuration. Connecting one to an existing workspace requires replacement review; do not assume arbitrary root files survive. These examples are not silently converted to app releases.

First installations still take time for upstream downloads and builds. Resource requirements apply to the target workspace for incremental installation and to the isolated builder for image mode. Installing a dependency is distinct from building a workspace image.

Existing image connections retain their strategy. Changing the manifest does not automatically adopt apps or migrate data; ownership and data handoff require explicit review. Blue-green remains an independent advanced workflow, not a stage in ordinary app deployment.

## Layout convention

Expand Down
4 changes: 4 additions & 0 deletions ai-chat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,7 @@ No required env — the AI credentials are injected by the managed proxy.
- Persistence: none — chat is in-memory per browser session.
- Install: deps install on deploy via the recipe `install:` step (layered on the base); unchanged redeploys skip it.
- Health: `GET /healthz` → `{"ok": true}`; the process binds `0.0.0.0:8080`.

## Persistent app releases

The manifest uses `workspace.deployment.strategy: incremental`. Deployment stages app files separately from your editable checkout, then briefly restarts affected services on their original ports. The workspace, SSH sessions, and unrelated files stay in place. App rollback restores a retained release, not database contents or external side effects.
4 changes: 4 additions & 0 deletions ai-chat/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fastapi==0.135.1
uvicorn==0.41.0
pydantic==2.12.5
httpx==0.28.1
10 changes: 8 additions & 2 deletions ai-chat/rig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ port: 8080
# Light FastAPI app behind the managed AI proxy — 1GB is plenty.
workspace:
resources: { ramMb: 1024, vcpuCount: 1, diskSizeMb: 3072 }
install: pip install --break-system-packages fastapi uvicorn pydantic httpx
start: uvicorn chat:app --host 0.0.0.0 --port 8080
deployment:
strategy: incremental
install: |
python3 -m venv --copies .venv
.venv/bin/python -m pip install -r requirements.txt
start: .venv/bin/uvicorn chat:app --host 0.0.0.0 --port 8080
health:
path: /healthz
timeoutSeconds: 30
Expand All @@ -22,3 +26,5 @@ params:
- { value: rigbox/default, label: "Default" }
- { value: rigbox/fast, label: "Fast" }
- { value: rigbox/free, label: "Free" }
dependencyInputs:
- requirements.txt
4 changes: 4 additions & 0 deletions bluegreen-blog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,7 @@ keeps your content.
- `.env.example` — template documenting `ADMIN_TOKEN` (never loaded).
- `.env.production` / `.env.staging` — demo per-stage tokens loaded by
`rig deploy --stage <stage>`.

## Deployment strategy

This example explicitly uses `workspace.deployment.strategy: image` because its installer changes system packages, global executable paths, or shared tool configuration. The badge review shows image replacement and requires permission before replacing an existing workspace root filesystem. It is not an incremental app release. Use a dedicated workspace and back up root-filesystem development files; persistent volumes are retained. Migrating this installer to app-local releases remains separate work.
4 changes: 3 additions & 1 deletion bluegreen-blog/rig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
workspace:
image: base
resources: { ramMb: 1024, vcpuCount: 1, diskSizeMb: 3072 }
deployment:
strategy: image

apps:
blog:
Expand Down Expand Up @@ -51,4 +53,4 @@ apps:
envVar: BUILD_FLAVOR
options:
- { value: classic, label: "Classic" }
- { value: aurora, label: "Aurora" }
- { value: aurora, label: "Aurora" }
4 changes: 4 additions & 0 deletions catalog-apps/cli/claude/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,7 @@ default).
workspace is reachable only via SSH on the rigbox gateway.
- **Onboarding bypass.** `hasCompletedOnboarding` + `bypassPermissionsModeAccepted`
are written by `install:` so `claude` is non-interactive on first launch.

## Deployment strategy

This example explicitly uses `workspace.deployment.strategy: image` because its installer changes system packages, global executable paths, or shared tool configuration. The badge review shows image replacement and requires permission before replacing an existing workspace root filesystem. It is not an incremental app release. Use a dedicated workspace and back up root-filesystem development files; persistent volumes are retained. Migrating this installer to app-local releases remains separate work.
2 changes: 2 additions & 0 deletions catalog-apps/cli/claude/rig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ kind: cli
# the agent has comfortable headroom for repo-wide edits and node_modules.
workspace:
resources: { ramMb: 1024, vcpuCount: 1, diskSizeMb: 3072 }
deployment:
strategy: image
# Bake the CLI into an image: `rig deploy` runs `install:` once in a builder VM
# and later deploys boot from the cached image instead of re-running the
# upstream installer. The script runs as `developer` with passwordless sudo.
Expand Down
91 changes: 6 additions & 85 deletions catalog-apps/cli/codex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,94 +2,15 @@

[![Deploy to Rigbox](https://rigbox.dev/deploy.svg)](https://rigbox.dev/deploy?repo=rigbox-dev%2Frigbox-examples&ref=main&path=catalog-apps%2Fcli%2Fcodex%2Frig.yaml)

Runs [**Codex CLI**](https://github.com/openai/codex) — OpenAI's lightweight
coding agent for the terminal — on Rigbox. The CLI lives inside the workspace;
you SSH in and run `codex`. It's the OpenAI-shaped counterpart to Claude Code:
small, fast, and natively speaks the OpenAI API, which makes it trivial to
point at Rigbox's **managed AI proxy** (an OpenAI-compatible endpoint) — no API
key to set, so deploying is never blocked on a local secret.

## The single capability: an OpenAI-shaped AI agent frozen into an image

The whole point here is **running an OpenAI-API-compatible AI CLI on a
persistent VM** — your repo, history, and `~/.codex/` config survive across
sessions and across deploys. `rig.yaml` sets `reproducible: true`, so
`rig deploy` runs the `install:` script once in a builder VM, freezes the
result as an image, and later deploys boot from it instead of re-running
`npm install`:

```yaml
reproducible: true
install: |
set -euo pipefail
npm install -g --no-fund --silent @openai/codex # → ~/.npm-global/bin/codex
sudo ln -sfn "$HOME/.npm-global/bin/codex" /usr/local/bin/codex
sudo tee /etc/profile.d/codex-routing.sh <<'EOF'
… # supplies $OPENAI_API_KEY, below
EOF
cat > "$HOME/.codex/config.toml" <<'EOF'
… # managed-AI provider, below
EOF
```

No Dockerfile — `install:` is the same script a plain deploy would run on the
VM (as `developer`, with passwordless `sudo` for the system-path steps);
`reproducible: true` is what makes `rig deploy` freeze its result.

## SSH-in to use it

Codex CLI is a TUI — there is **no web UI**. The app is declared with
`kind: cli`, so `rig.yaml` carries no `port`, `start`, or `health` — the
platform doesn't expect an HTTP front door. The real UX is SSH:

```bash
ssh "$(rig workspace ssh-info --workspace <name-or-id> --output json | jq -r .ssh_target)"
codex
```

## Managed AI routing, no key to set

`rig.yaml` opts into the workspace's managed AI proxy:

```yaml
ai:
managed: true
```

Codex 0.137+ reads its provider from `~/.codex/config.toml`, not env vars, and
only speaks the Responses wire. `install:` writes a config that declares the
managed proxy as a custom provider (plain HTTP, no websocket):

```toml
model = "anthropic/claude-sonnet-4.5"
model_provider = "rigbox"

[model_providers.rigbox]
base_url = "http://172.16.0.1:9090/v1"
env_key = "OPENAI_API_KEY"
wire_api = "responses"
```

The API key comes from `$OPENAI_API_KEY`; the `/etc/profile.d/codex-routing.sh`
that `install:` writes sources the managed proxy's `~/.rigbox/proxy.env` to
supply the placeholder. An SSH session just works — no key, no `export` dance.

## Deploy
Install Codex CLI 0.153.4 as a managed app release in a persistent workspace. Dependencies live under the release directory; no sudo, global npm installation, or shell-profile modification is needed.

```bash
cd codex && rig deploy
cd catalog-apps/cli/codex
rig deploy
```

No secret required — `ai: managed: true` routes Codex through the workspace's
managed AI proxy (your account's AI mode must be `managed`, which is the
default).
Then SSH into the workspace and run `codex` from your development checkout. Rigbox registers the `executables.codex` entrypoint and preserves command arguments, terminal input, and the invoking directory. `codex.sh` supplies the versioned managed-AI provider configuration through command-line options, while authentication and interactive state remain in the user's home directory. The workspace supplies the managed proxy environment; no private provider key is committed here.

## Notes
There is no HTTP port or preview URL. Activating another release changes subsequent CLI invocations; an already-running command keeps its current process. App rollback switches the registered entrypoint to a retained release. Your development files and SSH session remain in place.

- **Persistence: yes.** `~/.codex/` (history, session state) lives on the
workspace disk, outside the rsync zone — durable across redeploys.
- **No public UI.** `kind: cli` means there's no HTTP front door at all — the
workspace is reachable only via SSH on the rigbox gateway.
- **`OPENAI_API_KEY` inside the VM is a placeholder** (`managed-by-rigbox`); the
managed proxy authenticates by source IP, not by the key, and meters usage to
your account.
This manifest explicitly selects incremental deployment. Existing image-based installations require reviewed ownership migration; they are not silently adopted or overwritten.
10 changes: 10 additions & 0 deletions catalog-apps/cli/codex/codex.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
release_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
exec "$release_dir/node_modules/.bin/codex" \
-c 'model_provider="rigbox"' \
-c 'model="rigbox/default"' \
-c 'model_providers.rigbox.name="Rigbox Managed AI"' \
-c 'model_providers.rigbox.base_url="http://172.16.0.1:9090/v1"' \
-c 'model_providers.rigbox.env_key="OPENAI_API_KEY"' \
-c 'model_providers.rigbox.wire_api="responses"' "$@"
Loading