- Best for
- One server, for you or a small team
- Setup
- One compose file: Ryph, a launcher and Postgres
- Runs
- Containers on the server’s Docker
- Isolation
- Runs on a confined network. One process holds every key; a launcher holds the Docker socket.
- Data
- Postgres in a container
- Scale
- One machine
This mode runs the same ryph binary as a laptop, in a container beside a real Postgres. Agents run as containers on the same machine’s Docker. The host only needs Docker.
It suits one server you keep running, such as a NAS, a homelab box or a VPS, for one person or a small team. It’s the simplest way to self-host Ryph.
The files are in deploy/compose/ in the Ryph repository.
What you need
- Docker Engine 28 or later, which confining runs needs, with Compose 2.20 or later and buildx. Docker Desktop has all three.
- 10 to 15 GB of disk for the images. Most of it is the agent image, which carries every mainstream toolchain. Environment builds need several GB more for each repository.
- An x86_64 or arm64 machine.
- The
compose.yamlfromdeploy/compose/, and its.env.exampleif you want to change settings. Every image is pulled fromquay.io/ryph.
Start it
cd deploy/compose
cp .env.example .env # optional; every setting has a default
docker compose pull
docker compose up -d --wait docker compose pull also fetches the images runs and environment builds start from, so the first run doesn’t wait on a large download.
On Linux, set DOCKER_GID in .env to the group of the Docker socket first. stat -c %g /var/run/docker.sock prints it. The launcher starts runs through that socket, and the group is what lets it. Docker Desktop works with the default.
RYPH_VERSION in .env picks the version of every image at once. The default, edge, is the newest build of the main branch, and a sha- tag pins one commit. There are no numbered releases yet.
Claim it
The first account needs the setup token Ryph prints at boot:
docker compose logs ryph | grep "Setup token" The line holds a full link. Open it and register, and you’re the owner. Everyone after you registers or is invited from the web app.
Then add a model provider key under Model keys, connect a repository, and create an agent.
For one person, set RYPH_MULTIUSER=off in .env before the first start. Ryph is then single-user like the laptop binary, and you sign in with a link. docker compose exec ryph ryph login --no-open prints a fresh one.
What runs
Your server
The ryph container
One process, holding the master key and the database, and no Docker socket
- Web app
- Sign-in
- Control plane
- Gateways
- Run history
- Memory
- Chat
- Automations
- Forge
The launcher
The Docker socket, and none of Ryph’s secrets
- Launcher
postgres
On no network at all, reached through a socket
- PostgreSQL 17
Run network
Internal, and only the run gate leads out. Runs share it.
- Run
- Run
- Run gate
Build network
One per build, behind the same gate
- BuildKit
Tool network
Hosted MCP servers, on a network runs don’t join
- Tool guard
- MCP server
| Service | What it does |
|---|---|
ryph | The binary: the web app, sign-in, control plane, gateways, run history, memory, chat, automations and the forge, in one process. It holds the secrets and no Docker socket. |
launcher | The only container with the Docker socket. It starts the containers below when Ryph asks, and holds none of Ryph’s secrets. |
postgres | PostgreSQL 17. It’s on no network at all. Ryph reaches it through a socket in a shared volume. |
artifact-thumbnails | Renders the previews in artifact lists. It has no network, credentials or Docker socket, and only Ryph can reach its socket. |
The launcher starts the rest:
- One container per run, on an internal network with no outside access.
- The run gate, the only way out of a run’s network. It relays only Ryph’s run-facing ports.
- A browser beside a run whose agent uses one.
- Hosted MCP servers, behind a tool guard on a network runs don’t join.
- One BuildKit container per environment build, on its own network behind the same gate.
State lives in named volumes: the master key and files, the database, repositories kept on the server, and the launcher’s build cache.
Compose doesn’t know about the containers the launcher starts, so they outlive docker compose down. To remove everything, take them away by the labels the launcher puts on them. The README in deploy/compose/ has the commands.
Connect your code
GitHub, as you. Create a fine-grained personal access token for the repositories agents should work on, and set it as RYPH_GITHUB_TOKEN in .env. It needs read and write access to Contents, Pull requests and Issues, and read access to Metadata. Commit statuses (read) is optional and lets agents see CI. Leave Workflows off, so no agent can change .github/workflows. Everything Ryph does on GitHub appears as you. This mode gets no webhooks, so /ryph comments do nothing and merges are noticed about once a minute.
Repositories on the server. Without a git host, agents can work on repositories in the repos volume. Clone one there with docker compose exec ryph git clone, then add it under Repos → Connect repository → On this machine.
Radicle. The radicle profile adds a Radicle node of Ryph’s own, with its own identity. Ryph can also act through a node that already runs on the server.
GitHub as an App. An App acts as itself and gets webhooks, so /ryph commands and merges arrive straight away. It needs GitHub to reach your server, so a NAS on a home network usually can’t use one.
GitLab, as you. Works with gitlab.com or your own GitLab, 17.0 or newer. Create a personal access token with the api, read_repository and write_repository scopes, and set RYPH_GITLAB_MODE=user and RYPH_GITLAB_TOKEN in .env, plus RYPH_GITLAB_URL for your own instance. As with GitHub, everything appears as you, /ryph comments do nothing and merges are noticed by polling.
GitLab with a bot. A bot is a GitLab account of Ryph’s own, and it gets webhooks. Give it a token with the same scopes, and add it to your projects or a group at the Developer role, so protected branches keep it off your default branch. Set RYPH_GITLAB_TOKEN to its token and RYPH_GITLAB_WEBHOOK_SECRET to a random secret. When you connect a project, Ryph shows a webhook URL and token for a Maintainer to paste into the project’s Settings → Webhooks. For sign-in with GitLab, add an OAuth application with the read_user scope and set RYPH_GITLAB_CLIENT_ID and RYPH_GITLAB_CLIENT_SECRET.
A branch Ryph pushes to GitLab runs the project’s .gitlab-ci.yml, an agent’s edits to it included, with the project’s unprotected CI variables. Protect the variables a pipeline shouldn’t hand to such a branch.
Reach it from other machines
The web app listens on 127.0.0.1:3000, so only the server reaches it. To open it to others:
-
Put a proxy with TLS in front
Ryph’s session cookie only works over HTTPS, so plain HTTP on a LAN address signs you out straight away.
-
Set the app's address
Set
RYPH_APP_URLto the HTTPS address people use. -
Pass on the visitor's address
Set
RYPH_ADDRESS_HEADER=x-forwarded-for, andRYPH_XFF_DEPTH=1behind one proxy. Sign-in is rate limited per address, and without this every visitor arrives from Docker’s gateway and shares one limit. -
Keep the app on localhost
Leave
RYPH_UI_BIND=127.0.0.1when the proxy runs on the same server.
With Caddy:
ryph.example.lan {
reverse_proxy 127.0.0.1:3000
} Webhooks for automations arrive on port 7411 under /v1/hooks/. Route that path from your proxy if you use them, but leave /v1/hooks/github and /v1/hooks/gitlab on the app’s port, ahead of it. The app hands those two on to the forge.
Environments
Each repository gets an environment: an image with its checkout and setup already done, so its runs start ready instead of installing dependencies every time. Checks run on the same images. Ryph builds them by itself, and again as the default branch moves. Both are on by default. RYPH_ENVIRONMENTS=off and RYPH_CHECKS=off in .env turn them off.
How it’s kept safe
Ryph holds the secrets, and the launcher holds the socket. The ryph container has the master key and the database, but no Docker socket. It asks the launcher over HTTP to start runs, checks, environment builds and hosted MCP servers. A request names only what to run. Everything that could reach past a container is the launcher’s own configuration: its networks, the run gate, the runtime, mounts and privileges.
So if Ryph is compromised, the attacker has every secret Ryph holds, but can only ask for what Ryph may ask for: an unprivileged run on the run network, or a hosted MCP server. It can’t ask for this machine’s files, a privileged container or a published port, so it doesn’t get root on the server through the launcher.
Runs are confined. Each run joins an internal Docker network with no default route, no DNS for outside names, and no address on the host. The launcher refuses to launch onto a network that isn’t. A run can only get out through the run gate, which reaches the control plane, the forge, the model and tool gateways and the egress proxy. It can’t reach the web app, previews or Postgres. The egress proxy lets a run reach only the hosts its agent’s network policy allows, and refuses private addresses.
Runs share that network, so a run can reach another run’s open ports. Runs use Docker’s default runtime, so an escape reaches the Docker daemon. On Linux with gVisor installed, RYPH_DOCKER_RUNTIME=runsc runs them under it. Builds don’t get that runtime, so with it set Ryph starts with environments on only if you also set RYPH_ENV_TRUST_DEFAULT_BRANCH=on, which builds just the default branch. The other choice is RYPH_ENVIRONMENTS=off.
Environment builds run privileged. A repository’s .ryph/Dockerfile and setup steps run as root in a BuildKit container. It’s confined like a run, but an escape from it is root on the server. Connect only repositories whose .ryph/ you’d run as root here, or turn environments off.
Whoever controls the launcher container controls the machine, so it holds no other secrets and listens only for Ryph. Keep it patched. To keep runs and services apart from the machine and from each other, use Kubernetes.
Upgrade
docker compose pull
docker compose up -d --wait If RYPH_VERSION is pinned, change it first. Ryph applies new migrations at boot and refuses to start on a database a newer version has migrated, so take a backup before you upgrade. Repos and workspaces with environment builds need a Rebuild afterwards to pick up the new agent tools.
Back up
Keep the database dump and the master key in different places. Together they hold your secrets in the clear.
# The database. Safe while Ryph runs.
docker compose exec -T postgres pg_dump -U ryph ryph | gzip > ryph-$(date +%F).sql.gz
# The master key, which opens every provider key and credential in the database.
docker compose exec ryph cat /var/lib/ryph/secrets.key > secrets.key The ryph-home and repos volumes hold run files and repositories kept on the server. Back them up too if you use them. The launcher’s volumes hold only build cache and working files.
docker compose down keeps every volume. docker compose down -v deletes them, the database and the key included.