Appearance
Dorkyfile services
A guide to declaring the services a machine runs. It covers what a flat service is, the keys that express one in a single block (run, bundle, files, packages), the dir shortcut, and how dorky apply --plan shows you the exact set of files that will ship before anything moves.
The file, and the loop
You author one file per machine, named <name>.dorky.toml — for example media.dorky.toml. Three commands make up the loop:
- Preview —
dorky apply media.dorky.toml --planprints the ship manifest: every file that will travel and where it lands. It is a pure local read of the file and the files beside it, so it works in an empty directory with nothing set up. - Apply —
dorky apply media.dorky.tomlprovisions the machine if it does not exist yet and converges it to the file. - Reach — the machine answers at
https://media.dorky.host. Its name is its subdomain.
A few terms this guide leans on:
- Machine — the small VM at the network's edge that runs your services. The top-level
namein the file (or the filename) names it. - Substrate — where a machine runs:
fly,unikraft, or a devsprite. Omit[provider]to take the default, or pin it with a[provider]table. <home>/$DORKY_HOME— the machine's home directory, the root everything you ship lands under.
On the machine, your <name>.dorky.toml is retained as dorkyfile.toml. That is the name you see for it in every ship manifest.
What a service is
A [[service]] block declares one long-running process (or a set of static files) the machine serves. A machine's Dorkyfile can hold as many as you need. The machine hosts them side by side, each under its own working directory at <home>/services/<name>/.
There are two ways to write a service, and they mean the same thing:
- Flat form — every field inline in the
[[service]]block. This is the plain way to write a service, and everything else is built on it. - The
dirshortcut —dir = "./x"points at a folder that carries its ownservice.toml. The machine reads that folder'sservice.tomland fills in the same fields the flat form spells out.
If you are starting fresh, write the flat form. Reach for dir when a service is already a self-contained folder you want to keep together.
The flat form, key by key
toml
[[service]]
name = "downloader" # required for a flat service
run = "bun ${DORKY_SERVICE_DIR}/app.js" # the start command, run verbatim
port = 8813 # the port the process listens on
bundle = "./app.ts" # optional: build one .js from this entry
files = ["assets/*.png"] # optional: ship files alongside the process
packages = ["ffmpeg"] # optional: tools this service needs on PATH
routes = { "/download" = "token" } # optional: paths this service ownsnameis the service's name, and the folder it lives in on the machine. A flat service must name itself; leave it out and the apply stops at parse with a message telling you to add it.runis the command that starts the process, written and executed verbatim by the machine's shell. Reference the service's own directory as${DORKY_SERVICE_DIR}and its listen port as${DORKY_<NAME>_PORT}. A service with aruncommand needs a validport, andrunshould be a process that stays up (a server), since the machine relaunches a service that exits.portis the port the process listens on. The machine exports it and routes to it.bundlebuilds one file from a TypeScript entry (see Shipping code).filesships files alongside the process (see Shipping files).packagesnames tools the service needs onPATH(see Declaring tools).routesare the paths this service owns and how each is gated (public,token,owner,byo-validator). A service with norunis a static service: it serves files or routes and runs no process.
Reading the port
The machine assigns each service its listen port and exports it as an environment variable named after the service: DORKY_<NAME>_PORT, where <NAME> is the service name uppercased with every - turned into _.
| Service name | Port variable |
|---|---|
downloader | DORKY_DOWNLOADER_PORT |
site | DORKY_SITE_PORT |
upstream-gateway | DORKY_UPSTREAM_GATEWAY_PORT |
Your process reads that variable and listens on it. A Bun service:
ts
const port = Number(process.env.DORKY_DOWNLOADER_PORT);
Bun.serve({ port, fetch: () => new Response("ok\n") });Or straight in the run command, when the program takes the port as an argument:
toml
run = "python3 -m http.server ${DORKY_SITE_PORT}"A run-only service is legal
The smallest service is a name and a run command — no code folder, no bundle, no files:
toml
[[service]]
name = "clock"
run = "python3 -m http.server ${DORKY_CLOCK_PORT}"
port = 8814
packages = ["python3"]The machine creates an empty working directory at <home>/services/clock/, exports the port, resolves the python3 package onto PATH, and starts the command. Nothing is shipped; the process is all there is.
Shipping code (bundle)
bundle points at a TypeScript entry file, relative to the Dorkyfile:
toml
[[service]]
name = "downloader"
run = "bun ${DORKY_SERVICE_DIR}/downloader.js"
bundle = "./downloader.ts"
port = 8813When you apply, the CLI builds that entry with bun build and ships only the single output .js into the service's folder. The compiler inlines the entry's whole import graph into that one file. Your source .ts files, package.json, lockfiles, and node_modules never travel. This is the answer for a service that lives in a larger repo: you ship one built file, not the repo.
The output filename comes from your run command (the .js it launches); if run names none, it is the entry's name with .ts swapped for .js. The entry path must be relative to the Dorkyfile.
Shipping files (files)
files ships named files and globs. Entries are paths or globs relative to the Dorkyfile's directory, and each matched file lands at the same relative path under the scope's root.
- Machine scope —
filesat the top level of the Dorkyfile. Lands under<home>/files/. - Service scope —
filesinside a[[service]]block. Lands under that service's own folder,<home>/services/<name>/.
toml
name = "studio"
files = ["samples/*.wav"]
[[service]]
name = "site"
run = "bun ${DORKY_SERVICE_DIR}/serve.js"
port = 8080
files = ["serve.js", "web/index.html"]Here the machine-scope samples/*.wav land under <home>/files/, while the site service ships both the code its run launches (serve.js) and the page it serves (web/index.html) into its own folder. Notes:
- Ship the file your
runlaunches. Nothing is shipped implicitly, so arunthat startsserve.jsonly works ifserve.jsis listed infiles(or built bybundle). - Globs expand on your computer at apply time. The bundle carries concrete files; what you see in the ship manifest is exactly what travels.
- The path is mirrored, never renamed.
samples/kick.wavlands atfiles/samples/kick.wav. - A wildcard never ships junk.
.git,node_modules, any.envor*.env, and OS debris are dropped even when a greedy glob would match them, so a secret cannot enter through a wildcard. Secret values arrive only throughdorky secret set, never a file. - A literal that matches nothing is an error; an empty glob is allowed. Absolute paths and paths that escape the Dorkyfile's directory are rejected.
Declaring tools (packages)
packages names the command-line tools a service needs on its PATH. A machine has one environment shared by all its services. So per-service packages fold up into that one environment: the machine resolves the union of every service's packages plus any declared at the top level.
These two Dorkyfiles produce the same environment:
toml
# declared per service — reads next to the code that needs it
packages = ["yt-dlp"]
[[service]]
name = "transcriber"
run = "uv run transkun ..."
packages = ["transkun", "ffmpeg"]toml
# declared once at the top — equivalent
packages = ["yt-dlp", "transkun", "ffmpeg"]Declaring a package next to the service that needs it keeps the Dorkyfile readable; the machine merges them either way. Resolution happens at apply time against the machine's own system, so --plan names the packages but does not resolve them.
The dir shortcut
dir = "./x" is a shortcut for the flat form. The machine reads ./x/service.toml and fills in the flat fields from it; anything you also write inline in the [[service]] block wins over the harvested value.
toml
[[service]]
dir = "./services/upstream-gateway"is equivalent to a flat block whose name, run, port, secrets, and routes come from ./services/upstream-gateway/service.toml, with the folder shipped as the service's on-machine tree. The flat form is the canonical expression; dir is the sugar, and you never have to use it.
Serving static files
A service with no run command is static: it serves files or routes and runs no process. Point a route at a directory with dir, and the machine serves that directory with a file server. This is how docs.dorky.host — the site you are reading — serves itself:
toml
[[service]]
name = "docs"
files = ["www/docs.dorky.host/dist/**"]
[service.routes]
"/" = { mode = "public", dir = "www/docs.dorky.host/dist" }The files glob ships the built site into the service's folder, and the route roots a public file server at that directory. No process runs, so a static-only machine needs no runtime.
Previewing what ships — dorky apply --plan
Before anything ships, dorky apply <file> --plan (also spelled --dry-run) prints the ship manifest: every file that will travel, where it lands on the machine, and its size. It reads only the Dorkyfile and the files beside it, so it needs no machine and nothing configured. This is how you confirm a glob expanded to what you expected, before the fact.
For the studio example above, previewing prints:
plan studio.dorky.toml → "studio"
bundle dorkyfile.toml, site (files → services/site/), files → files/
ship 5 files:
dorkyfile.toml 167 B
files/samples/kick.wav 13 B
files/samples/snare.wav 14 B
services/site/serve.js 194 B
services/site/web/index.html 31 BEvery landed byte traces to a files, bundle, or dir reference you wrote. Nothing ships implicitly, and there is no "copy the current directory" path. The file set and its landing paths are the invariant to check: sizes come from a reference run and track your own file bytes.
Quick reference
| Key | Scope | Meaning |
|---|---|---|
name | service | The service's name and folder. Required for a flat service. |
run | service | Start command, run verbatim. Needs a port; should stay up. |
port | service | The listen port, exported as DORKY_<NAME>_PORT. |
bundle | service | A TS entry (Dorkyfile-relative) built to one .js. |
files | machine + service | Paths/globs (Dorkyfile-relative), path-mirrored under the scope root. |
packages | machine + service | Tools on PATH; service-scope folds up into the machine's one environment. |
routes | service | The paths a service owns and how each is gated. |
dir | service | Shortcut: harvest a folder's service.toml into the flat form. |
Landing paths: machine files → <home>/files/; a service's tree → <home>/services/<name>/. Preview any apply with dorky apply <file> --plan — no machine required.