Runtimes
The shell, go, node, and docker runtimes and the build, run, and watch defaults each contributes.
Every service has a runtime that names its lifecycle owner. Empty means shell. The other three read a typed block of the same name and synthesize commands, watch defaults, and managers. Whatever a runtime contributes is a default, so anything you set explicitly wins. Each runtime resolves the same defaults blink init writes, so a minimal hand-written block behaves like a generated one.
shell (default)
The fallback. When runtime is empty or shell, blink runs whatever you put under commands: as sh -c. The shell runtime contributes no overlay, no watch defaults, and no manager.
- name: worker
runtime: shell
commands:
run:
command: ./worker
service: true # a long-running process, not a one-shot
reload:
reload: true
fs:
extensions: [go]Warning
Reload is not implied by a runtime. A shell service that should restart on edits needs an explicit reload: true and an fs block. The go, node, and docker runtimes set this up for you.
go
Synthesizes a build plus binary-run pair, and when the repo uses a go.work workspace, adds every workspace module as an extra watch root so a cross-module edit rebuilds.
- name: api
runtime: go
go:
package: ./cmd/api # required
args: ["--port", "8080"]
out: ./bin/api # optional, defaults under .blink/build
workspace: true # optional, auto-detected from go.workBuild is go build -o <out> <package>, run is <out> <args...> marked as a long-running service. The default watched extensions are go, mod, sum. With a go.work present, blink parses its use directives and adds each module (except the service's own dir) as a recursive watch root. Set workspace: false to opt out even when a go.work exists.
node
Detects the package manager from the lockfile and synthesizes <pm> run <script>, optionally preceded by a guarded install.
- name: web
runtime: node
node:
script: dev # optional, defaults to dev then start
package_manager: pnpm # optional, auto-detected from lockfile
install: true # optional, defaults to trueWhen script is unset the runtime reads package.json and picks dev if present, else start. Package-manager detection order is pnpm, then bun, then yarn, then npm, keyed on the first lockfile present, falling back to npm. Install is a setup step, so it runs once on boot and again only when package.json changes, never on a source reload.
A self-reloading dev server (vite, next, nuxt, astro, remix, sveltekit, parcel, nodemon, tsx watch, --watch, and similar) is detected from the resolved command. blink then assumes the tool handles hot reload itself and scopes the watch to package.json only, so source edits never restart the process. A plain node server keeps a broad js/jsx/ts/tsx/mjs/cjs/json/css/html watch.
docker
Drives docker compose, watches container state via docker events, and by default streams every container's logs multiplexed into the UI. File-change reload is off, because compose owns the containers.
- name: infra
runtime: docker
docker:
file: docker-compose.yml # optional, probed when empty
project: myapp # optional, defaults to basename of dir
services: [db, redis] # optional, empty = all in compose file
logs: [db] # optional, empty = stream all containers
wait: true # optional, defaults true
stop_on_exit: false # optional, defaults false
log_tail: 500 # optional, defaults 500, <=0 = allservices and logs are distinct axes. services controls which compose services are started, and logs controls which running containers blink follows in the UI. stop_on_exit defaults false so containers persist between runs and the next boot reuses warm databases, and pre-existing containers blink did not start are never touched.
When file is unset the runtime probes compose.yaml, compose.yml, docker-compose.yaml, docker-compose.yml in that order and falls back to docker-compose.yml when none exist.
Full field tables for every runtime block live in the runtime reference.