Skip to content

alexgorbatchev/devhost

Repository files navigation

devhost

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.

Quick start

Installation

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

Requirements

  • either:
    • a global caddy on your PATH, or
    • a managed Caddy binary downloaded with devhost caddy download
  • nvim and curl when [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.

Minimal example

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.

Build from source

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 --version

That 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.

AI bootstrap skill

To install the manifest-authoring bootstrap skill from this repository:

npx skills add https://github.com/alexgorbatchev/devhost --skill devhost-bootstrap -y

Omit -y to choose target agents interactively.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors