Skip to content
Merged
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
6 changes: 3 additions & 3 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ local services, service overrides, and named volumes there. install.sh creates
this file once and never changes it. Your changes survive every install and
upgrade.

install.sh refreshes the repo compose files on each run. So do not edit
`compose.yaml`, `compose.local.yaml`, or `compose.remote.yaml` — those changes
are lost on the next install.
install.sh refreshes the repo compose files on each run. Do not edit
`compose.yaml` or `compose.docker-sbx.yaml`. The next install overwrites
these files.

Example — bake locally-installed apps into the web image:

Expand Down
11 changes: 7 additions & 4 deletions backend/druks/setup_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"DRUKS_WEB_BIND_HOST",
"DRUKS_DOCKER_GID",
"COMPOSE_FILE",
"COMPOSE_PROFILES",
"DRUKS_SBX_HOME",
)
_ENV_KEY_PATTERN = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")

Expand Down Expand Up @@ -212,7 +214,7 @@ def _fresh_values(*, provider: str, home: str) -> tuple[tuple[tuple[str, ...], s
# browser flows built from the endpoint (the GitHub manifest
# callback's BroadcastChannel) are origin-scoped.
(("urls", "endpoint"), "http://127.0.0.1:8001"),
(("sandbox", "service_url"), "http://127.0.0.1:8000"),
(("sandbox", "service_url"), "http://127.0.0.1:8780"),
(("sandbox", "service_token"), "dev-token"),
(("sandbox", "image"), "ghcr.io/czpython/druks-sandbox:latest"),
)
Expand Down Expand Up @@ -354,9 +356,10 @@ def _render_env(
) -> str:
provider = _get_string(config, ("sandbox", "provider"))

service_tokens = ""
if provider != "docker":
service_tokens = _get_string(config, ("sandbox", "service_token"))
# Rendered on every shape. drukbox requires SERVICE_TOKENS and does not
# start without it. A compose-side default would replace that safe stop
# with a known token.
service_tokens = _get_string(config, ("sandbox", "service_token"))

sections = (
(
Expand Down
11 changes: 8 additions & 3 deletions backend/tests/test_setup_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ def test_docker_shape_matches_local_wiring_and_ignores_provider_environment(tmp_
values = read_env(env_path)
assert values["DEFAULT_HOST_PROVIDER"] == "docker"
assert "DRUKS_AUTH_HEADER" not in values
assert "SERVICE_TOKENS" not in values
# Rendered on every shape. Without it, drukbox stops instead of falling
# back to a known token.
assert values["SERVICE_TOKENS"] == "dev-token"
assert "DOCKER_HOST" not in values


Expand Down Expand Up @@ -311,15 +313,18 @@ def test_compose_plane_env_additions_survive_rerender(tmp_path):
_run(env_path, provider="docker")
env_path.write_text(
env_path.read_text()
+ "DRUKS_UID=1000\nDRUKS_DOCKER_GID=988\nCOMPOSE_FILE=compose.yaml:compose.local.yaml\n"
+ "DRUKS_UID=1000\nDRUKS_DOCKER_GID=988\nDRUKS_SBX_HOME=/home/op\n"
+ "COMPOSE_FILE=compose.yaml:compose.override.yaml\nCOMPOSE_PROFILES=hosted\n"
)

_run(env_path)

values = read_env(env_path)
assert values["DRUKS_UID"] == "1000"
assert values["DRUKS_DOCKER_GID"] == "988"
assert values["COMPOSE_FILE"] == "compose.yaml:compose.local.yaml"
assert values["DRUKS_SBX_HOME"] == "/home/op"
assert values["COMPOSE_FILE"] == "compose.yaml:compose.override.yaml"
assert values["COMPOSE_PROFILES"] == "hosted"
assert "# OPERATOR ADDITIONS" in env_path.read_text()


Expand Down
44 changes: 29 additions & 15 deletions deploy/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
# Druks Deployment

The base `compose.yaml` is Druks (`web`, which embeds the DBOS durable engine
and serves the dashboard SPA), Postgres, and Redis. A shape overlay adds the
Drukbox sandbox control plane on top: `install.sh` writes `COMPOSE_FILE` to
`.env` (e.g. `compose.yaml:compose.remote.yaml`), so plain `docker compose`
commands in the install dir load the right pair.

A **remote** install (any `DRUKS_PROVIDER` except `docker`) uses
`compose.remote.yaml`: the Drukbox control plane (`drukbox`, `drukbox-janitor`)
against a cloud provider, plus stock Caddy (identity edge + proxy, Caddyfile
bind-mounted).

A **local** install (`DRUKS_PROVIDER=docker`) uses `compose.local.yaml`: one
`drukbox` with the host's Docker socket mounted, so sandboxes are sibling
containers on the host daemon, and no Caddy — the dashboard is reached directly
on `127.0.0.1:8001`. See [Full local](../docs/full-local.md).
`compose.yaml` holds the full stack: Druks (`web`, which embeds the DBOS
durable engine and serves the dashboard SPA), Postgres, Redis, the Drukbox
sandbox control plane (`drukbox`), the janitor, the SSH gateway, and the Caddy
edge. `install.sh` writes `COMPOSE_PROFILES` to `.env`. Then plain
`docker compose` commands in the install directory do the correct thing.

A **local** install (`DRUKS_PROVIDER=docker`) runs bare, with no profiles.
`drukbox` mounts the Docker socket of the host. Sandboxes are sibling
containers on the host daemon. The dashboard is on `127.0.0.1:8001`, with no
Caddy. See [Full local](../docs/full-local.md).

A **remote** install (each other `DRUKS_PROVIDER`) enables
`COMPOSE_PROFILES=hosted`: the Drukbox control plane against a cloud provider,
the periodic janitor, and stock Caddy (identity edge and proxy, with the
Caddyfile bind-mounted).

The **docker-sbx** provider also layers `compose.docker-sbx.yaml` and enables
the `gateway` profile. The overlay connects the Drukbox services to the
[Docker Sandboxes](https://docs.docker.com/ai/sandboxes/) daemon of the host
(microVM sandboxes). The gateway is the SSH path into them. Prepare the host
first: install `docker-sbx`, put the service user in the `kvm` group, then run
`sbx login` and `sbx daemon start -d --policy balanced`. The installer stops
with a clear message when the daemon socket is missing.

Before this layout, each shape had its own overlay file. Those files are
retired: `compose.local.yaml` is now the base with no profiles, and
`compose.remote.yaml` is the base with `COMPOSE_PROFILES=hosted`. Deployments
that fetch compose files by path must update to `compose.yaml` (and
`compose.docker-sbx.yaml` for that provider).

Drukbox keeps its own schema in a `drukbox` database in the same Postgres, so
there is no second datastore to run or back up separately.
Expand Down
49 changes: 49 additions & 0 deletions deploy/compose.docker-sbx.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# The Docker Sandboxes overlay. It connects the drukbox services, and the
# gateway that compose.yaml declares behind the "gateway" profile, to the
# sandboxd daemon on the host. sandboxd runs as one user. The overlay mounts
# four things: the daemon socket, the sbx CLI of the host, the CLI auth store,
# and the workspace root. The mount of the host CLI keeps the CLI version and
# the daemon version equal. The auth store and the workspace root keep the
# same path inside and outside the container, because the daemon resolves
# paths on its own filesystem. The services run with the uid of the daemon
# owner (the deploy user). DRUKS_SBX_HOME is the home directory of that user.
# install.sh writes it to .env, and each compose command renders the same
# mounts, also from sudo or systemd. The docker.sock mount from the base
# stays: browser-login containers use the docker provider.

x-sbx-rig: &sbx-rig
user: "${DRUKS_UID:?set DRUKS_UID in .env — run install.sh}:${DRUKS_GID:?set DRUKS_GID in .env — run install.sh}"
volumes:
- ${DRUKS_SBX_HOME:?set DRUKS_SBX_HOME in .env — run install.sh}/.local/state/sandboxes/sandboxes/sandboxd/sandboxd.sock:/run/sandboxd.sock
- /usr/bin/sbx:/usr/local/bin/sbx:ro
- ${DRUKS_SBX_HOME:?}/.config/com.docker.sandboxes:${DRUKS_SBX_HOME:?}/.config/com.docker.sandboxes:ro
- ${DRUKS_SBX_HOME:?}/.drukbox:${DRUKS_SBX_HOME:?}/.drukbox

# The image has no user with the deploy uid, and defaults that come from the
# home directory resolve nowhere. XDG_CONFIG_HOME points the sbx CLI to the
# mounted auth store. The workspace root is set here, and it cannot disagree
# with the mount above.
x-sbx-env: &sbx-env
XDG_CONFIG_HOME: ${DRUKS_SBX_HOME:?}/.config
DOCKER_SANDBOXES_API: unix:///run/sandboxd.sock
DOCKER_SBX_WORKSPACE_ROOT: ${DRUKS_SBX_HOME:?}/.drukbox/sbx-workspaces

services:
drukbox:
<<: *sbx-rig
environment:
<<: *sbx-env

drukbox-janitor:
<<: *sbx-rig
environment:
<<: *sbx-env

# GATEWAY_SSH_HOST is the address the provider gives to callers. It comes
# from .env through the [sandbox.docker-sbx] table. Provisioning refuses to
# start without it.
drukbox-gateway:
<<: *sbx-rig
environment:
<<: *sbx-env
GATEWAY_HOST_KEY_PATH: ${DRUKS_SBX_HOME:?}/.drukbox/gateway_host_key
41 changes: 0 additions & 41 deletions deploy/compose.local.yaml

This file was deleted.

69 changes: 0 additions & 69 deletions deploy/compose.remote.yaml

This file was deleted.

Loading