Skip to content
Open
194 changes: 192 additions & 2 deletions src/content/docs/dev/admin/configuration/execution-backend.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,196 @@ title: Execution Backend

import { Aside } from "@astrojs/starlight/components";

<Aside type="note" title="Coming Soon">
This documentation is under construction.
ricochet runs user content — apps and tasks — in an **execution backend**.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ricochet runs user content — apps and tasks — in an **execution backend**.
ricochet runs content items in an **execution backend**.

Three backends are supported:

| Backend | `engine` value | Use when |
| ------------ | -------------- | ----------------------------------------------------------------------------------------------------------------- |
| Host | (omitted) | Single host, no container daemon. Content runs as isolated processes on the host via [libcontainer][libcontainer]. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| Host | (omitted) | Single host, no container daemon. Content runs as isolated processes on the host via [libcontainer][libcontainer]. |
| Host | (omitted) | Content runs as a rootless OCI container. |

| Container | `container` | Single host with a Docker- or Podman-compatible daemon. Content runs as full containers spawned through the daemon. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| Container | `container` | Single host with a Docker- or Podman-compatible daemon. Content runs as full containers spawned through the daemon. |
| Container | `container` | Single host with a Docker or Podman-compatible daemon. Content runs as a container spawned through the daemon. |

| Kubernetes | `k8s` | Multi-node cluster, content scheduled as Pods. |

[libcontainer]: https://docs.rs/libcontainer/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[libcontainer]: https://docs.rs/libcontainer/


If `[backend]` is omitted, ricochet uses the **host** backend.
Content runs as isolated processes managed in-process by `libcontainer` (an OCI-runtime implementation, no daemon required), using the language interpreters installed on the host.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Content runs as isolated processes managed in-process by `libcontainer` (an OCI-runtime implementation, no daemon required), using the language interpreters installed on the host.
Content runs a rootless OCI-compatible container using the language interpreters installed on the host.

This page documents the `container` and `k8s` backends; the host backend has no configuration knobs of its own.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This page documents the `container` and `k8s` backends; the host backend has no configuration knobs of its own.

semi-colons—the new AI tic.


<Aside type="tip" title="Default exec-env images">
The container and Kubernetes backends pull an OCI image per content item.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The container and Kubernetes backends pull an OCI image per content item.

I don't think this is necessary

ricochet publishes a continuously-updated set of production-ready images at [ricochet-rs/exec-envs](https://github.com/ricochet-rs/exec-envs) — R on Alpine, Ubuntu, and AlmaLinux; Python on Ubuntu and AlmaLinux — rebuilt weekly and suitable as defaults for `default_image` and per-content `exec_env` references.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ricochet publishes a continuously-updated set of production-ready images at [ricochet-rs/exec-envs](https://github.com/ricochet-rs/exec-envs) — R on Alpine, Ubuntu, and AlmaLinux; Python on Ubuntu and AlmaLinux — rebuilt weekly and suitable as defaults for `default_image` and per-content `exec_env` references.
ricochet publishes a continuously-updated set of production-ready images at [ricochet-rs/exec-envs](https://github.com/ricochet-rs/exec-envs) which are rebuilt weekly and are suitable defaults.

Bring your own images when you need additional system libraries or a different language/version combination.
</Aside>

## Host vs container on a single host

Both backends run content on the same machine as ricochet-server.
The difference is **how** content is isolated and **where** the runtime environment comes from.

| Dimension | Host backend (`libcontainer`) | Container backend (`container`) |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| Dimension | Host backend (`libcontainer`) | Container backend (`container`) |
| Dimension | Host backend | Container backend (`container`) |

| ------------------------ | ---------------------------------------------------------------------- | ---------------------------------------------------------------- |
| Isolation mechanism | `libcontainer` namespaces/cgroups, set up in-process by ricochet-server | OCI containers, spawned by the host's Docker/Podman daemon |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| Isolation mechanism | `libcontainer` namespaces/cgroups, set up in-process by ricochet-server | OCI containers, spawned by the host's Docker/Podman daemon |
| Isolation mechanism | Namespace and cgroup with PID isolation | OCI containers, spawned by the host's Docker/Podman daemon |

| Daemon required | No | Yes (Docker, Podman, or compatible) |
| Runtime / interpreters | Whatever is installed on the host (system R, Python, Julia) | Whatever the chosen image provides; arbitrary per content item |
| Per-content exec env | Single shared host environment | Per-item via `exec_env` → image, like the Kubernetes backend |
Comment on lines +37 to +38

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These seem redundant. The "per-content exec-env" row can probably be deleted?

Comment on lines +37 to +38

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| Runtime / interpreters | Whatever is installed on the host (system R, Python, Julia) | Whatever the chosen image provides; arbitrary per content item |
| Per-content exec env | Single shared host environment | Per-item via `exec_env` → image, like the Kubernetes backend |
| Interpreters | Discovered on the host | Defined by the execution environment |

| Observability | Processes are not visible to `docker ps` / container metrics tooling | Each app/task is a real container — visible to standard tooling |
| Setup cost | None — works on a fresh ricochet-server install | Install and run a container daemon, grant socket access |

Pick the **host** backend for simple single-tenant deployments where the host already has the interpreters you need and you do not want to operate a container daemon.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Pick the **host** backend for simple single-tenant deployments where the host already has the interpreters you need and you do not want to operate a container daemon.
Use the **host** backend for deployments where the host already has the interpreters you need and you do not want to operate a container daemon.


Pick the **container** backend when you need different runtime environments per content item (e.g. R 4.3 vs R 4.5, conflicting system libraries), or when you want each running app and task to be a visible, individually observable container.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Pick the **container** backend when you need different runtime environments per content item (e.g. R 4.3 vs R 4.5, conflicting system libraries), or when you want each running app and task to be a visible, individually observable container.
Use the **container** backend when you want each running app and task to be a visible in an observable container.

Different runtimes are already supported.


## Container backend

Set `engine = "container"` to spawn content as containers via a local Docker- or Podman-compatible daemon.

```toml title="ricochet-config.toml"
[backend]
engine = "container"
default_image = "r-default"
exec_env_config_path = "ricochet-exec-env.toml"
content_path = "/var/lib/ricochet/data/content"
cache_path = "/var/lib/ricochet/data/.cache"
```

| Field | Required | Description |
| ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `engine` | yes | Must be `"container"`. |
| `default_image` | no | Image category name from `exec_env_config_path`. Used when a content item does not specify its own exec environment. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "category" language needs to be removed 😭

Suggested change
| `default_image` | no | Image category name from `exec_env_config_path`. Used when a content item does not specify its own exec environment. |
| `default_image` | no | An image name from the `ricochet-exec-env.toml` to be used as a default when no `exec-env` is specified. |

| `exec_env_config_path` | yes | Path to the exec-env config TOML. Absolute, or relative to `RICOCHET_HOME`. |
| `content_path` | yes | Host path bind-mounted into every spawned container at `/opt/ricochet/data/content`. Must contain ricochet's deployment bundles. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| `content_path` | yes | Host path bind-mounted into every spawned container at `/opt/ricochet/data/content`. Must contain ricochet's deployment bundles. |
| `content_path` | yes | Path that is bind-mounted into every spawned container. Must contain ricochet's deployment bundles. |

| `cache_path` | yes | Host path bind-mounted into every spawned container at `/opt/ricochet/cache`. Stores R, Julia, and Python package caches shared across content. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| `cache_path` | yes | Host path bind-mounted into every spawned container at `/opt/ricochet/cache`. Stores R, Julia, and Python package caches shared across content. |
| `cache_path` | yes | Path bind-mounted into every spawned container. Stores R, Julia, and Python package caches shared across content. |

| `proxy_host` | no | Hostname ricochet's reverse proxy uses to reach spawned content containers. Defaults to `localhost`; override when running ricochet-server in a container (see [Deployment scenarios](#deployment-scenarios)). |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| `proxy_host` | no | Hostname ricochet's reverse proxy uses to reach spawned content containers. Defaults to `localhost`; override when running ricochet-server in a container (see [Deployment scenarios](#deployment-scenarios)). |
| `proxy_host` | no | Hostname ricochet's reverse proxy uses to reach spawned content containers. Defaults to `localhost`. Override when running ricochet in a container (see [Deployment scenarios](#deployment-scenarios)). |


ricochet refuses to start if `exec_env_config_path`, `content_path`, or `cache_path` is missing.

### Image resolution

`default_image` and per-content `exec_env` values are looked up in the exec-env config.
A category like `r-default` resolves to a concrete image reference (`docker.io/ricochetrs/r-alpine:4.5`).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A category like `r-default` resolves to a concrete image reference (`docker.io/ricochetrs/r-alpine:4.5`).
For example, `r-default` resolves to a concrete image reference (`docker.io/ricochetrs/r-alpine:4.5`).

If the value does not match a category, ricochet treats it as a full image reference and uses it as-is.

### Exec-env config

The exec-env config TOML maps category names to concrete container images and declares the language runtimes inside each image.
Content items pick a category via their `exec_env` setting; ricochet falls back to `default_image` when none is set.

The example below references the [official ricochet exec-env images](https://github.com/ricochet-rs/exec-envs) — adjust the image and tag, or substitute your own.

```toml title="ricochet-exec-env.toml"
[image.r-default]
image = "docker.io/ricochetrs/r-alpine:4.5"
os = "alpine-3.23"
arch = ["linux/amd64", "linux/arm64"]
description = "Default R 4.5 environment (Alpine)"
r = [
{ version = "4.5.3", bin = "/usr/bin/R" },
]

[image.r-44]
image = "docker.io/ricochetrs/r-alpine:4.4"
os = "alpine-3.23"
arch = ["linux/amd64", "linux/arm64"]
r = [
{ version = "4.4.3", bin = "/usr/bin/R" },
]

[image.python-default]
image = "docker.io/ricochetrs/python-ubuntu:3.13"
os = "ubuntu-26.04"
arch = ["linux/amd64", "linux/arm64"]
description = "Default Python 3.13 environment (Ubuntu)"
python = [
{ version = "3.13", bin = "/usr/local/bin/python3" },
]
```

`r`, `python`, and `julia` arrays declare the interpreter versions present in the image and the path to their binaries — ricochet uses these to match a content item's required version against an available runtime.

### Deployment scenarios

How ricochet-server itself is deployed shapes the container-backend setup.
The ricochet-server install is covered on its own page — only the container-backend specifics are below.

#### ricochet-server via systemd (on the host)

Set up ricochet-server first — see [Host installation](/dev/admin/installation/1-host/).

Container-backend specifics:

- The daemon and ricochet-server share the host's filesystem, so `content_path` and `cache_path` resolve consistently on both sides — nothing to translate.
- The user running ricochet-server needs read/write access to the daemon socket (typically by adding it to the `docker` group, or running as root as the default systemd unit does).
- `proxy_host` can stay at its default `localhost` since both processes share the host's network namespace.

#### ricochet-server in a container (sibling containers)

Set up ricochet-server in a container first — see [Container installation](/dev/admin/installation/2-container/).

ricochet-server runs as a container and uses the host's daemon to spawn sibling containers for user content — sometimes called "Docker-out-of-Docker" (DooD).
Three container-backend specifics apply:

1. **Bind-mount the daemon socket** (`/var/run/docker.sock`) into the ricochet-server container.
2. **Match host and ricochet-server paths for `content_path` and `cache_path`.** The daemon resolves these against the **host** filesystem, not ricochet-server's view, so the simplest correct setup mounts the host data directory at the same path inside the ricochet-server container.
3. **Set `proxy_host` to an address ricochet-server can reach.** Sibling content containers publish their app ports to the host, not to ricochet-server's loopback. Either run ricochet-server with `--network host` (default `proxy_host` works), or use `--add-host=host.docker.internal:host-gateway` together with `proxy_host = "host.docker.internal"`, or set `proxy_host` to the host's IP.

```sh title="Bridge network with host-gateway alias"
docker run -d \
--name ricochet-server \
-p 6188:6188 \
--add-host=host.docker.internal:host-gateway \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/ricochet:/var/lib/ricochet \
docker.io/ricochetrs/ricochet-server:latest
```

```toml title="ricochet-config.toml (DooD)"
[backend]
engine = "container"
exec_env_config_path = "ricochet-exec-env.toml"
content_path = "/var/lib/ricochet/data/content"
cache_path = "/var/lib/ricochet/data/.cache"
proxy_host = "host.docker.internal"
```

## Kubernetes backend

For multi-node clusters, use `engine = "k8s"`.
ricochet creates a Deployment per app and a Job per task using the cluster's API.

```toml title="ricochet-config.toml"
[backend]
engine = "k8s"
namespace = "ricochet"
default_image = "r-default"
exec_env_config_path = "/etc/ricochet/exec-env.toml"
pvc_content_name = "ricochet-content"
pvc_cache_name = "ricochet-cache"
image_pull_policy = "IfNotPresent"
```

The same [`ricochet-exec-env.toml`](#exec-env-config) format applies, and the [official exec-env images](https://github.com/ricochet-rs/exec-envs) work unchanged as Pod images — they're built multi-arch (amd64/arm64) and ship with the same runtimes used by the container backend.

See [Kubernetes installation](/dev/admin/installation/3-kubernetes/) for cluster prerequisites, Helm chart values, and PVC sizing guidance.

## Known issues

<Aside type="caution" title="Path mismatch is silent">
Applies to the [container backend](#container-backend) — most often in the [DooD scenario](#ricochet-server-in-a-container-sibling-containers) where ricochet-server's filesystem view differs from the host's.

If `content_path` points to a path that does not exist on the host, the daemon still launches the spawned container — but bind-mounts the wrong (or empty) directory.
Content items will appear to deploy but fail at runtime because their bundles are not visible.
Verify `ls $content_path` works on the **host**, not just inside the ricochet-server container.
</Aside>

<Aside type="caution" title="Wrong proxy_host is silent too">
Applies to the [container backend](#container-backend) in the [DooD scenario](#ricochet-server-in-a-container-sibling-containers) only — see [Field reference](#container-backend) for the `proxy_host` setting.

If `proxy_host` resolves but does not actually reach the host (typo, wrong IP, missing `--add-host`), spawned content containers start and pass health checks at the container layer, but ricochet's reverse proxy gets a connection refused or timeout when it tries to route a request — surfacing as 502s in the UI.
</Aside>

<Aside type="note" title="Remote daemons not yet supported">
Applies to the [container backend](#container-backend). The [Kubernetes backend](#kubernetes-backend) is the supported path for multi-host execution today.

ricochet currently only talks to a daemon on the same host as ricochet-server.
Remote daemon support is on the roadmap.
</Aside>
102 changes: 99 additions & 3 deletions src/content/docs/dev/admin/installation/2-container.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,104 @@
title: Container
---

import { Aside } from "@astrojs/starlight/components";
import { Aside, Tabs, TabItem } from "@astrojs/starlight/components";

<Aside type="note" title="Coming Soon">
This documentation is under construction.
Run ricochet-server itself as a container.
This is the same `ricochet-server` binary as the [host install](/dev/admin/installation/1-host/), packaged as an image.

## Image

```
docker.io/ricochetrs/ricochet-server:<version>
```

Tags follow the ricochet release version (e.g. `v0.7.3`).
A `latest` tag tracks the most recent stable release.

## Required volumes

Two host directories must be persisted across container restarts:

| Mount target | Purpose |
| ----------------------- | --------------------------------------------------------------------------- |
| `/var/lib/ricochet` | Database, deployment bundles, encryption keys, file logs. |
| `/var/lib/ricochet/data/.cache` | R, Julia, and Python package caches. Share across content runs. |

The cache directory lives under `RICOCHET_HOME` by default, so mounting `/var/lib/ricochet` covers both.
Separate them only if you want the cache on a different volume (e.g. faster local disk vs. networked storage).

## Ports

ricochet-server listens on **6188** by default.

## Minimal run

```sh
docker run -d \
--name ricochet-server \
-p 6188:6188 \
-v /var/lib/ricochet:/var/lib/ricochet \
docker.io/ricochetrs/ricochet-server:latest
```

Place your `ricochet-config.toml` at `/var/lib/ricochet/ricochet-config.toml` on the host.
ricochet-server reads it on startup from the bind-mounted location.
Comment on lines +35 to +46

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I still can't get ricochet-server to run code in a container. I don't think this should be published yet.


## Spawning user content

The execution backend ricochet uses for spawning user content (apps, tasks) is independent of how ricochet-server itself runs.

### Host (default)

With no `[backend]` block in `ricochet-config.toml`, ricochet-server runs content as [libcontainer](https://docs.rs/libcontainer/)-managed isolated processes **inside** its own container, using the interpreters baked into the ricochet-server image.
Suitable for small single-tenant setups where every content item can share the same runtime environment.

See [Host vs container on a single host](/dev/admin/configuration/execution-backend/#host-vs-container-on-a-single-host) for the tradeoff.

### Container backend (sibling containers)

To spawn each piece of user content as its own container on the host, configure the [container backend](/dev/admin/configuration/execution-backend/#container-backend), mount the host's container daemon socket into ricochet-server, and make sure ricochet-server can reach the host network so it can proxy to spawned containers:
Comment on lines +59 to +61

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this covered by the DooD section?


```sh
docker run -d \
--name ricochet-server \
-p 6188:6188 \
--add-host=host.docker.internal:host-gateway \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/ricochet:/var/lib/ricochet \
docker.io/ricochetrs/ricochet-server:latest
```

Set in `ricochet-config.toml`:

- `content_path` and `cache_path` to paths that exist on the **host**.
With the example mount above, `content_path = "/var/lib/ricochet/data/content"` resolves to the same physical directory from both ricochet-server's view and the host daemon's view — sibling containers see the deployment bundles ricochet-server wrote.
- `proxy_host = "host.docker.internal"` so pingora reaches the host's published ports instead of ricochet-server's own loopback.

<Aside type="caution" title="Paths are host paths">
The daemon resolves `content_path` and `cache_path` against the host filesystem, not the ricochet-server container's view.
Mount the host data directory into the ricochet-server container at the **same path** used in the config, or sibling containers will mount empty directories and content will fail to start.
</Aside>

See the [execution backend reference](/dev/admin/configuration/execution-backend/#ricochet-server-in-a-container-sibling-containers) for `proxy_host` alternatives (`--network host`, explicit host IP) and the full field reference.

### Kubernetes backend

For multi-node setups, do not run ricochet-server as a standalone container — use the [Kubernetes installation](/dev/admin/installation/3-kubernetes/) instead.

## Compose example

```yaml title="compose.yml"
services:
ricochet-server:
image: docker.io/ricochetrs/ricochet-server:latest
container_name: ricochet-server
ports:
- "6188:6188"
extra_hosts:
- "host.docker.internal:host-gateway" # only for container backend
volumes:
- /var/lib/ricochet:/var/lib/ricochet
- /var/run/docker.sock:/var/run/docker.sock # only for container backend
restart: unless-stopped
```