Self-hosting
Run the open-source Dock control plane and hosts on your own machines.
Dock is open source (AGPL-3.0) and self-hosting is free. The whole
control plane runs on one machine with Docker Compose from deploy/self-host/
in the Dock source; its README.md is the full guide. This page summarizes it.
Before exposing an instance, read the threat model.
| Service | Role |
|---|---|
db (Postgres 16), redis | durable state, rate limits |
restate | durable Dock workflows (internal only) |
minio + minio-init | Snapshot storage, bucket created on start |
migrate | applies database migrations, then exits |
backend | REST API, fleet API, GitHub webhooks, workflow endpoint |
restate-register | registers the workflow endpoint with Restate on every start |
dashboard | web UI |
caddy | one public origin: /v1/*, /api/auth/*, /openapi.json to the API, the rest to the dashboard |
Requirements
- Docker with Compose v2.
- For a public instance, a DNS name pointing at the machine (for example
dock.example.com) and ports 80 and 443 free, so Caddy can obtain a public certificate. Plain HTTP works for trying Dock, but GitHub webhooks and confined GitHub runs need HTTPS on 443. - To run Docks on the same machine: Linux with Docker (
gvisor) or/dev/kvm(microvm).
Quick start
From deploy/self-host/:
./setup.sh --host # local: http://localhost:8088, Docks on this machine
./setup.sh --url https://dock.example.com # public instance, automatic HTTPSsetup.sh writes .env with fresh secrets on the first run, pulls the images, starts the stack,
and waits until the API is healthy. --host enrolls the same machine as a
host (it asks for sudo, and the first run builds the Dock image; it expects a
usedock/docklet checkout next to this repository at ../docklet: clone
https://github.com/usedock/docklet there, or set DOCKLET_DIR).
Re-running keeps .env. To configure by hand, copy .env.example to .env,
fill it, and run docker compose up -d.
The stack runs prebuilt images (ghcr.io/usedock/dock-api, dock-api-migrate,
and dock-dashboard, for linux/amd64 and linux/arm64) at the release in
DOCK_VERSION, pinned in .env.example. To build from source instead, run
./setup.sh --build.
Open DOCK_PUBLIC_URL and sign up. On a self-hosted instance:
- There is no billing on a self-hosted instance.
- Telemetry is off by default.
- Mail is optional. With
SMTP_HOSTempty, messages such as sign-in codes and reset links are written to the API log (docker compose logs backend | grep -A2 "mail not delivered"). - Google and GitHub sign-in are optional. A provider appears only when both
its client id and secret are set; the callback URL is
<DOCK_PUBLIC_URL>/api/auth/callback/<google|github>. - SSH listens on
DOCK_SSH_PORT(default 2222); open it in your firewall. The host key is derived fromBETTER_AUTH_SECRET, so it survives rebuilds; setSSH_HOST_KEYin.envto pin your own. - Hosted ports work on a local install at
*.ports.localhost. On a public install, run./setup.sh --ports-domain ports.example.comwith a wildcard DNS record*.ports.example.compointing at the server. Caddy issues each certificate on first use, only for exposed names.
Upgrade by setting DOCK_VERSION in .env to the new release and running docker compose pull && docker compose up -d; migrations run before
the API starts. Back up the pgdata and minio-data volumes and keep .env.
Installs created before the single baseline migration (October 2026) cannot
upgrade in place: the migration fails and the API never starts. Reset with
docker compose down -v (this deletes all Docks, Snapshots, and accounts), run
./setup.sh again, and re-enroll each host.
Keep SECRETS_ENCRYPTION_KEY
Losing it makes stored provider keys and Environment secrets unreadable.
Add hosts
A host is a machine that runs Docks. It dials out to DOCK_PUBLIC_URL and needs
no inbound port. ./setup.sh --host enrolls the control plane machine itself.
On any other Linux machine, from a checkout of usedock/docklet (FLEET_TOKEN
is in .env):
sudo bash scripts/bootstrap-host.sh \
--fleet-url https://dock.example.com \
--fleet-token "$FLEET_TOKEN" \
--runtime microvmThe --runtime flag picks what the host provides:
microvm: Firecracker on Linux KVM (bare metal or nested virtualization with/dev/kvm). The whole disk persists. The installer builds the image (several minutes, about 20 GB).gvisor:runscon any Linux machine with Docker. Only/home/user/workpersists across stop and resume.
The runtime is a property of the host, not of a Dock: a Dock gets the runtime of the host it lands on. See Runtimes and images. The docklet repository covers hosts in depth.
GitHub issue to pull request
Each instance registers its own GitHub App:
- Put your email in
INSTANCE_ADMIN_EMAILSin.envand rundocker compose up -d backend. - In the dashboard open Developers > GitHub App > Register App and continue on GitHub.
- Paste the App id, slug, private key, and webhook secret GitHub returns into
.env(they are shown once and not stored by Dock), then rundocker compose up -d backend. - Install the App on your repositories and link the installation to a workspace on Developers > GitHub App.
- Add a model provider key under Developers > Provider keys.
Mention @dock in an issue comment to start a run.
Network notes
- GitHub webhooks go to
<DOCK_PUBLIC_URL>/v1/github/webhooks, so the API must be reachable from github.com. - Docks used by GitHub runs reach the internet only through each host's egress
proxy, which allows HTTPS to an allowlist that always includes the control
plane. The control plane must therefore serve HTTPS on 443 with a publicly
trusted certificate. If
DOCK_PUBLIC_URLresolves to a private address, allow that range on each host with--egress-allow-private 10.0.0.0/24. - Snapshot transfers go through the API's object proxy by default. Set
SNAPSHOT_STORE_PUBLIC_ENDPOINTonly if hosts can reach the object store directly. - The stack uses
pgsty/minioandpgsty/mcimages; override withMINIO_IMAGE/MINIO_CLIENT_IMAGE, or pointSNAPSHOT_STORE_ENDPOINTat any S3-compatible bucket.