compose.yaml holds the full stack. It includes Druks, Postgres, Redis,
Drukbox, the janitor, the SSH gateway, and the Caddy edge. The web service
contains the DBOS durable engine and serves the dashboard SPA. The drukbox
service is the sandbox control plane. 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.
A remote installation uses each other DRUKS_PROVIDER value. It enables
COMPOSE_PROFILES=hosted. This profile includes a remote Drukbox control plane,
the periodic janitor, and stock Caddy. Caddy supplies the identity edge and
proxy with a bind-mounted Caddyfile.
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 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. Run
sbx login. Then run 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 the base with no profiles.
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.
The Druks services use the host network so they can connect to Postgres, Redis,
Drukbox, and provider-specific sandbox addresses from the host network
namespace. The exe.dev shape reaches VMs over the host tailnet. Other providers
can return SSH addresses that are directly reachable.
First-time setup on a fresh box
Prerequisites: Docker with the Compose plugin. The exe.dev shape also needstailscaled joined to the intended tailnet (tailscale status shows
peers). Other remote providers have their own network and credential
requirements.
For the local Docker shape, see Full local.
The image registry provides Druks service and sandbox images for both
linux/amd64 and linux/arm64.
install.sh handles everything else. This work includes compose.yaml, the
Caddyfile, druks.toml, the rendered .env, image pulls, and DB initialization.
1. Run the installer
docker is the default provider for the local shape. A remote deployment names
its provider. Use exe for exe.dev. Use another Drukbox provider name for the
generic remote shape. The first pass writes ~/druks/druks.toml with generated
secrets.
It creates ~/druks/.env and exits if required values are missing.
The output identifies each missing value. Edit druks.toml. For a generic
remote shape, fill [sandbox.<provider>] from the Drukbox
configuration reference.
After boot, connect the GitHub App that Druks uses from Settings → Services.
Use the permission table in Configuration.
The sandbox backend defaults to the local docker shape. On the first run, set
DRUKS_PROVIDER to select another shape. Use exe for exe.dev with Tailscale.
Use another Drukbox provider name for the generic remote shape. Druks passes this
name through without a provider registry.
Later runs read [sandbox].provider
from druks.toml. Thus, the environment flag only seeds a new installation.
If you want another installation directory, set
DRUKS_INSTALL_DIR=/srv/druks.
2. Re-run the installer
.env from druks.toml and validates the required
values. Then it runs docker compose pull. It migrates the databases with
docker compose run --rm web druks init-db. A remote installation also migrates
the Drukbox schema. Finally, it runs docker compose up -d. Startup does not
run migrations.
3. Make sure that the stack operates
4. Expose the public surfaces
On exe.dev, one port-share carries both dashboard and webhooks:https://<host>/_external/{github,linear,jira}/events/. The PAT-authenticated MCP
endpoint uses https://<host>/mcp. See
Connect your agent. The dashboard uses
https://<host>/. exe.dev authenticates at the edge. Druks maps the asserted
email to your account.
On another remote provider, the dashboard uses your identity proxy. Set
[identity].header in druks.toml to the injected header. Webhook senders
cannot authenticate through SSO. They require a separate public HTTPS path.
The stack Caddy provides this path:
The proxy must strip any client-supplied copy of that identity header before
inserting its authenticated value. It must also terminate TLS and set HSTS.
Druks’ shipped Caddy dashboard listener is loopback HTTP behind that edge.
Configure public webhook access:
- Point an A record, such as
druks.example.com, at the host. - Open inbound ports 80 and 443.
- Set
[urls].webhook_host = "druks.example.com"indruks.toml. - Run the installer again.
POST /_external/* and the PAT-authenticated /mcp endpoint. It does not serve
dashboard routes or the identity header. Thus, a public client cannot forge the
SSO gate.
Webhook URLs become
https://druks.example.com/_external/<provider>/events/. Agents connect at
https://druks.example.com/mcp
(Connect your agent). Leave
[urls].webhook_host blank to bring your own ingress instead.
Update / redeploy
Editdruks.toml. Then run the installer from the version that you will deploy.
The installer creates .env and refreshes compose.yaml and the Caddyfile. It
applies new migrations with docker compose run --rm web druks init-db. A remote
installation also migrates Drukbox.
Then the installer pulls the images and
starts the stack. Compose replaces only changed services. To migrate without
the installer, run docker compose run --rm web druks init-db.
Recreating web interrupts in-flight execution. DBOS recovers compatible
workflows from completed checkpoints when the process returns. Changes to
workflow structure, step order, step names, or serialized input can break
compatibility.
Before such a deployment, drain affected runs. You can also keep
an executor with compatible code until the runs finish. Recovery does not
preserve a live agent process inside a sandbox. It follows the operation
boundary in
Concepts.
One-time: upgrading a box that ran the backend as root
The backend containers use the deployment user (DRUKS_UID and DRUKS_GID),
not root. A host from an older deployment can have root-owned files. The
non-root containers must write to logs/ and prompt-cache/ in the data
directory.
install.sh sets DRUKS_UID and DRUKS_GID. It also changes the
owner of the sandbox-keys volume. The script does not have root privileges. If
the host has root-owned files, run this command one time. If you changed
DRUKS_DATA_HOST_DIR, adjust the path:
sudo. Then run the normal upgrade with install.sh. It writes
DRUKS_UID and DRUKS_GID. It changes the owner of the sandbox-keys volume and
recreates the stack as the deployment user.
A new installation does not require
this step. install.sh and the deployment user own its files from the start.
main and latest are the edge channel. For a tagged installation, get the
installer from that tag. Set DRUKS_REF to the same tag. The installer selects
the related image tag. See Releasing Druks for the
immutable install shape.
Rollback
Each Druks commit has an image tag in the form:sha-<full-git-sha>. The image
contains both the API and the SPA build. It is one release artifact.
Pin a specific build by setting DRUKS_TAG in .env:
druks init-db
only upgrades migrations. It does not downgrade them. Before you select an
older image, make sure that its code can read the current schema. Make sure that
it can read the current druks.toml and .env. Active workflows must also be
compatible with that code.
Logs / stop
How the proxy routes
exe.dev exposes one port. Caddy enforces access for each path. It uses the stock image, host network, port:8000, and the Caddyfile from the installer:
- Webhooks: Druks exposes
POST /_external/*publicly. The matching webhook class authenticates each request. Per-provider paths land under/_external/<provider>/<category>/. App role-module discovery registers them at import time. - MCP: Druks exposes
/mcppublicly. A personal access token authenticates each request inside Druks. Caddy does not buffer this route, so its SSE frames stream. - Dashboard: Everything else requires a nonempty trusted identity header. The exe.dev
login supplies this header. Caddy proxies the request to
webat127.0.0.1:8001. This service supplies the API, SPA, and app frontends. Druks maps the asserted email to your account for each request (access control).