Skip to content

Getting Started

Planck is the reference implementation of Database with Embedded Wasm Runtime: your code runs in the same process as your data, every query is a function call, and the unit of deployment is one binary. Whether you are building a monolith, a set of services, or Self-Contained Systems, each unit is one Planck.

The runtime is a small set of pieces working together: an HTTP listener, a WASM runtime, and change streams. Operational tasks (backup, gc, WAL truncate, stats, export, import) are scheduled and run from workbench, which is the control plane. Do note that restore is driven from planctl, not workbench. Replication is its own continuous path between primary and replica, so it is not a scheduler task.

This page walks you from zero to a running host, and then through scaffolding and deploying your first service. On first install, kindly read it top to bottom. After that you can come back to specific sections as and when you need them.

What you're installing

One tarball (zip on Windows) ships three binaries plus a WASM runtime library:

  • planck: the storage engine, HTTP listener, WASM runtime, and change streams.
  • workbench: the control plane. It owns identity, supervision, the query editor, the deploy receiver, and the scheduler that drives operational tasks against each connected planck. It listens on 2369 by default.
  • planctl: the CLI you will use for host setup, project scaffolding, and deploys.

A libwasmer shared library sits next to the binaries. planck loads the same at runtime when an app hosts WASM. planctl deploy copies it into each deployed app's directory, so the running instance always finds it as a sibling.

Download

Pick the archive that matches your machine from the releases page.

macOS

macOS is a local / dev target, so install under your home directory, with no sudo and no /opt. ~/.planck/bin is exactly where planctl system init looks for the binaries.

sh
mkdir -p ~/.planck
tar -xzf ~/Downloads/planck-0.1.0-macos-arm64.tar.gz
 -C ~/.planck --strip-components=1

Add the bin directory to your shell rc (~/.zshrc):

sh
export PATH="$HOME/.planck/bin:$PATH"

Linux

Linux is the production target, so install under /opt/planck:

sh
sudo mkdir -p /opt/planck
sudo tar -xzf ~/Downloads/planck-0.1.0-linux-amd64.tar.gz
 -C /opt/planck --strip-components=1

Add the bin directory to your shell rc (~/.bashrc):

sh
export PATH="/opt/planck/bin:$PATH"

Reload your shell, and then check that the binaries are on PATH:

sh
which planck planctl workbench

Windows (PowerShell as Administrator)

powershell
Expand-Archive -Path "$HOME\Downloads\planck-0.1.0-windows-amd64.zip
" -DestinationPath .
New-Item -ItemType Directory -Force -Path 'C:\Program Files\Planck'
Move-Item .\planck-0.1.0-x86_64-windows\bin 'C:\Program Files\Planck\bin'

planctl system init expects the binaries at C:\Program Files\Planck\bin. Then open Advanced System Settings, go to Environment Variables, and add C:\Program Files\Planck\bin to both the User and System Path entries.

Initialize the host

Before you can deploy anything, you need a workbench (the control plane) and a system database running on this host. One command sets up both:

sh
planctl system init

What system init does:

  1. Lays out the data directory under the OS-specific install root ($HOME/.planck on macOS, /opt/planck on Linux, C:\Program Files\Planck on Windows).
  2. Drops a default config.yaml for workbench. (Config is YAML, always. There are no env-var overrides.)
  3. Brings up the system database on port 23469.
  4. Registers planck and workbench as OS-managed services (launchd on macOS, systemd on Linux, or the Windows Service Manager) and starts them.

The install root and service manager are chosen automatically by the host OS, and there is no flag to override the same. (On macOS it registers launchd daemons too; it does not spawn the processes directly.)

Bring everything up:

sh
planctl system start

The workbench listens on http://127.0.0.1:2369 by default. Open that in a browser and log in as the admin user with the built-in default admin key, and then rotate it immediately. The default is well-known and baked into the engine, so do not leave it as is.

Other lifecycle commands you will want:

sh
planctl system stop      # stop everything supervised
planctl system start     # bring it all back up
planctl system deinit    # unregister services; binaries and data files stay on disk

Configure a deploy profile

planctl deploy talks to a workbench. It picks the target from ~/.planctl/config.yaml. The file declares one or more profiles, and each profile lists the workbench nodes it should reach.

For a local install, this much is enough:

yaml
profiles:
  - name: dev
    nodes:
      - server: http://127.0.0.1:2369
        uid: admin
        key: <paste-the-admin-key-here>

Every command that hits a workbench takes --profile <name>.

Your first service

Scaffold a hypermedia monolith. It is the fastest way to see the whole stack working end to end:

sh
planctl new mystore --type hda --arch mono
cd mystore

The four template combinations:

TypeArchWhat you get
hdamonoServer-rendered HTML (datastar) + WASM app hosted by planck. One deployable.
hdamicroShell binary, per-feature WASM services, plus an SSE service for live updates.
spamonoVue 3 SPA + single JSON API + WASM app. One deployable.
spamicroVue 3 SPA + per-feature JSON services + SSE service.

Templates do not ship with a reverse proxy, and planctl new does not pick one for you. In dev, the shell, SSE service, and any per-feature services each bind their own port, and the browser reaches them directly via CORS. In production, drop in your own Caddyfile, nginx.conf, or Traefik config.

Before the first deploy, set the ports. The scaffolded app/db.yaml (planck wire port) and app/service.yaml (user-facing HTTP port) both ship as 0, and the pre-deploy validator rejects 0. As such, give each a unique non-zero port.

Now deploy it to the workbench you brought up earlier. planctl deploy builds, uploads, and restarts the supervised processes in one step, so you need not run zig build first. Pick the target flag that matches whatever has changed.

Deploy just the app (mono = WASM hosted by planck; micro = native shell binary):

sh
planctl deploy --app --arch mono --profile dev

Deploy a single service by name (micro projects only):

sh
planctl deploy --service orders --arch micro --profile dev

Deploy just the SSE subproject (handy for fast iteration when you are tweaking templates or topic logic and do not need to rebuild the app):

sh
planctl deploy --sse --arch mono --profile dev

Deploy everything (the app, every services/* for micro, and the SSE subproject if the project has one):

sh
planctl deploy --all --arch mono --profile dev

--all runs the targets in order: --app, then each --service <name> (micro), and then --sse last so that the topic is alive before browsers connect. For a brand-new mono project this is the simplest first deploy. After upload, workbench restarts the supervised processes.

Once that returns, your app is live. Open it in a browser, or hit a route with curl, on the HTTP port you set in app/service.yaml (e.g. 3010):

sh
curl -k https://127.0.0.1:3010/

The -k is needed because of the self-signed cert. From the workbench UI on 2369 you can also browse the schema, run PQL queries against the app's data, and watch live stats.

A taste of the code

Every HTTP route in a Planck app is a handler with the same signature:

zig
fn handle(ctx_ptr: ?*anyopaque, allocator: std.mem.Allocator, req: *const Request, res: *Response) !void {
    // ...
}

Responses have a small, deliberate surface: html, json, and write. There is no render method.

A typical hypermedia handler renders a ZSX fragment and writes it back as HTML:

zig
var out: std.ArrayList(u8) = .empty;
try fragment.render(&out, allocator);
try res.html(out.items);

Queries are built with PQL. You construct a query against the in-process client and chain operators:

zig
var q = try planck.Query.initWithAllocator(client, allocator);
const tasks = try q
    .where("status", .eq, "open")
    .orderBy("created_at", .desc)
    .limit(20)
    .skip(0)
    .select(&.{ "id", "title", "status" })
    .run();

.where(field, op, value), .orderBy, .limit, .skip, .select, .run. That is the whole surface, nothing more.

Where to go next

  • Add a feature or service to the project you just scaffolded with planctl add.
  • PQL reference for the full operator set and the change-stream API.
  • ZSX guide for the template language and how fragments are generated.
  • Workbench tour for the supervision, backup, and identity UIs.

Welcome to Planck. Build something small first; the stack rewards iterating in tight loops.