// Getting started
Self-hosting
Stand up the control plane with Docker Compose, then attach a host that can boot microVMs.
dummie is self-hostable end to end. There are two things to stand up: the control plane, which is a container like any other, and at least one host that can boot microVMs, which needs hardware virtualisation.
The control plane
The control plane ships as a single image. It needs PostgreSQL for its own records, ClickHouse for event history, and an S3-compatible store for user artifacts — all three are in the compose file in the repository root:
git clone https://github.com/codingcoffee/dummie
cd dummie
docker compose -f docker-compose.prod.yml up -d
The control service reads its configuration from control/.env, and the three
backing services read theirs from .postgres.env, .clickhouse.env and
.rustfs.env. Create those before the first up; the compose file does not
carry defaults, on purpose — there are no credentials committed to this
repository for you to forget to change.
The API and the console both come up on port 1323.
Migrations are embedded in the binary rather than applied from a directory, so there is no separate migration step to run and no way for the schema to drift from the code that expects it.
ClickHouse users
Two different things talk to ClickHouse, over two different protocols, with two different sets of credentials. Keeping them separate is deliberate.
- The control plane reads event history to answer the console's panels, and
applies the ClickHouse migrations on startup. It uses
CLICKHOUSE_URLfromcontrol/.env— the native protocol, port9000— and because it owns the schema, that account needs DDL rights as well asSELECT. - vector, on every host, writes events and nothing else. It never reads
CLICKHOUSE_URL. Its endpoint, user and password come from the admin console under Settings, and are compiled into thevector.yamlthe control plane pushes to each host. That is the HTTP interface, port8123.
So the URL in the admin settings is not the control plane's own connection, and
the two are usually not even the same address: the settings one has to be
reachable from every QEMU host, while CLICKHOUSE_URL only has to be reachable
from the control container.
The writer account only ever inserts, so grant it only that:
CREATE USER vector_writer IDENTIFIED WITH sha256_password BY 'a-strong-password';
GRANT INSERT ON dummie.suricata_events TO vector_writer;
GRANT INSERT ON dummie.dns_queries TO vector_writer;
Then put vector_writer and its password into the ClickHouse user and password
settings in the console, and the ClickHouse URL as the hosts reach it —
http://10.0.0.1:8123, say. Leaving the URL empty stops vector being installed
at all, which is how you turn event shipping off.
Three notes on that grant:
SELECTis not needed, and should not be given.vector.yamllands on every host in the fleet,0600, read by a root process. The credential in it is only as private as the least private machine you run. An insert-only account caps a host compromise at forging events; one withSELECThands over every guest's traffic history, and one with DDL hands over the tables.- Don't grant on
dummie.*. Those two tables are the whole write path.domainonsuricata_eventsis aMATERIALIZEDcolumn the server computes, so vector never sends it and needs nothing extra for it. - The account's profile must allow per-query settings. vector sends
input_format_skip_unknown_fieldson every insert, so a profile withreadonly = 1, or with constraints on that setting, rejects the write even though the grants are correct. The stockdefaultprofile is fine.
Running the CREATE USER and GRANT above needs an account with access
management enabled. On the official image that means CLICKHOUSE_ACCESS_MANAGEMENT=1
in .clickhouse.env; without it, define the second user in a
/etc/clickhouse-server/users.d/ file instead.
Behind a reverse proxy
Put the control plane on its own hostname — console.<your-domain> is the
convention this project uses. Two things matter:
- TLS. The session cookie is issued
Securein production, and a browser will discard aSecurecookie that arrives over plain HTTP. Without TLS, sign-in appears to succeed and then does nothing. - Host header. Pass it through unmodified. Guest sessions are scoped by hostname, and rewriting it breaks the hand-off to a running VM.
Hosts
A host is a machine with nested virtualisation available, running dclient.
dclient registers with the control plane, then brings up the local services a
sandbox needs — proxy, dpipe, Suricata and CoreDNS — and manages their
configuration as VMs come and go. See Architecture for what
each of those owns.
Hosts need:
- KVM available to the kernel (
/dev/kvmpresent and writable) - Docker, for the Suricata container
- outbound reachability to the control plane
Trying it without hardware
The repository carries a Nix flake that builds a VM with nested virtualisation
enabled, which stands in for a real QEMU host. The control plane runs in Docker
on your machine, and dclient runs inside the VM:
just vm-start # boot the stand-in host
just vm-ssh # get a shell on it
just vm-stop # tear it down
This is the same code path as a production host, so it is a genuine test of a change rather than a simulation of one.
Configuring the marketing site
If you are hosting the whole project — this site included — the website image
takes a single variable, CONSOLE_URL, pointing at wherever your control plane
lives. It is applied to the built site when the container starts, so the same
image serves any deployment and never needs rebuilding to be re-pointed:
docker run -e CONSOLE_URL=https://console.example.com -p 8080:8080 \
codingcoffee/dummie-website