devhost gives your local app a proper front door: real hostnames, local HTTPS, and one command to start and route your dev services.
Use it when localhost:3000 stops being good enough: auth callbacks, cookie/domain behavior, multi-service stacks, or just wanting app.localhost and api.app.localhost to behave more like a real app. Any domain can work as well, as long as it is configured to resolve to the machine running devhost.
Documentation: alexgorbatchev.github.io/devhost
What it does well:
- routes local services onto HTTPS hostnames through managed Caddy
- starts one service or a full stack from
devhost.toml, including optional externally managed backends - waits for health checks before exposing managed routes
- optionally injects browser devtools for logs, service status, annotations, browser-hosted Neovim sessions, and aggregated third-party launcher buttons
The injected log minimap is intentionally a compact preview: each log entry stays on a single row and clips horizontally instead of wrapping into a full log viewer.
Download the archive for your platform from GitHub Releases, extract it, and place the devhost binary on your PATH.
Published GitHub Releases include versioned .tar.gz archives for darwin-arm64, linux-x64, linux-arm64, linux-x64-musl, and linux-arm64-musl.
To print the CLI build version:
devhost --version- either:
- a global
caddyon yourPATH, or - a managed Caddy binary downloaded with
devhost caddy download
- a global
nvimandcurlwhen[devtools.editor].ide = "neovim"
When Neovim editor integration is enabled, devhost loads a bundled devhost-react-highlight.nvim plugin for that
devhost instance. The plugin streams TSX/JSX cursor locations back to the injected browser overlay through the
instance's local control port and token, so multiple devhost stacks can run at the same time without sharing editor
state. The browser overlay matches React fiber source metadata first and falls back to fetchable source maps for
bundlers that do not expose fiber source locations. While the stack is running, devhost also writes an instance-scoped
shell launcher at .tmp/devhost/<stack-name>/nvim-shell/bin/devhost-nvim; run it from the project to open Neovim with
the same plugin, token, and project root as the browser-launched editor.
Configure your stack in devhost.toml, then run it through devhost.
name = "hello-stack"
[services.ui]
primary = true
command = ["bun", "run", "ui:dev"]
port = 3000
host = "foo.localhost"
dependsOn = ["api"]
[services.api]
command = ["bun", "run", "api:dev"]
port = 4000
host = "api.foo.localhost"
health = { http = "http://127.0.0.1:4000/healthz" }Most projects should add devhost to the relevant package.json so you can run it through the usual dev script from the directory that contains the manifest:
{
"scripts": {
"dev": "devhost"
}
}Then prepare Caddy once and start your stack:
devhost caddy download
devhost caddy trust
devhost caddy start
npm run dev
open https://foo.localhost(pnpm dev, yarn dev, and bun run dev work the same way when they invoke the same script.)
Manifest string values support environment-variable interpolation with {{ env.NAME }} placeholders. This applies to
string fields throughout the manifest, including hosts, paths, cwd values, command arguments, labels, and env maps.
Placeholder names must start with a letter or underscore and then use letters, digits, or underscores. Other text
stays literal, including malformed {{ ... }} sequences, and an unterminated {{ keeps the rest of that string
literal. Referencing an undefined valid placeholder is a manifest read error, while defined placeholders may expand to
the empty string.
Service commands are executed directly, not through an implicit shell. In practice that means
command = ["storybook", "dev", "--port", "$PORT"] passes the literal string $PORT as an argument, while
command = ["sh", "-c", "exec storybook dev --port \"$PORT\""] lets the shell expand $PORT from the child
process environment. Manifest interpolation still happens before launch, so {{ env.NAME }} inside command arguments
is also subject to the manifest interpolation rules above.
Important
devhost manages HTTPS routing through Caddy, not DNS.
Your chosen hostnames must already resolve to this machine or the browser will never reach the local proxy.
Good out-of-the-box choices are localhost and subdomains under *.localhost, such as foo.localhost and api.foo.localhost, because they work without additional DNS configuration.
On Linux, run devhost caddy privileged-ports once before the first HTTPS start if you want Caddy to bind privileged ports without running the whole stack as root.
If you are working from this repository and want a current-platform binary instead of a release download:
bun run compile:devhost
./apps/devhost/dist/devhost --versionThat build refreshes the embedded injected devtools bundle with Bun and writes the CLI binary to apps/devhost/dist/devhost with the version from apps/devhost/metadata.json embedded into devhost --version.
To install the manifest-authoring bootstrap skill from this repository:
npx skills add https://github.com/alexgorbatchev/devhost --skill devhost-bootstrap -yOmit -y to choose target agents interactively.