cortesi/devd: a local webserver for developers, reviewed for adoption
A local webserver for developers
At a glance
- What is it?
- devd is a single Go binary that serves static files and reverse proxies upstreams, with livereload, virtual hosting on localhost, and bandwidth simulation. It suits developers who want a terminal-first dev server without a runtime dependency.
- Who is it for?
- Adopt devd if you want one binary that serves a directory, proxies a few upstreams and injects livereload without adding Node or Python to the machine, and if you are comfortable with a tool whose latest tagged release is v0.9 from 2019-01-20 while the master branch still receives pushes (the last push was on 2026-06-21).
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 102 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem devd solves, and for whom
Front-end and full-stack work usually means running two or three things at once: a static file server, an application server, maybe a mock API. The usual answer is a runtime-heavy toolchain, or a pile of shell scripts that open ports and forget to close them. devd targets the developer who wants a single process to overlay all of that on one origin.
The README is explicit about the audience. devd is described as a single statically compiled binary with no external dependencies, released for macOS, Linux and Windows, and the stated motivation is not wanting to install Node or Python in a lightweight Docker instance just to serve files. That is the whole pitch: one binary copied onto PATH, no package manager, no config file, no daemonization.
It is also aimed at people who read logs in a terminal. The README says logs are colorized, span multiple lines, and can optionally include timing information and full headers. That is a deliberate choice against the quiet-by-default style of most dev servers, and it is the kind of thing you either value or find noisy.
How devd works: routes, injection and the watch loop
The core abstraction is the route specification, written as root=endpoint. A root can be a fixed path like /favicon.ico or a subtree like /images/, and the README notes the trailing slash matters. An endpoint is either a filesystem path or a URL to an upstream HTTP server. The command takes one or more of these as arguments, which is why the same binary can serve a directory, proxy an API and host a static tree in one invocation.
Virtual hosting is built on a trick rather than a config file. The README states that devd uses the domain localhost and all its subdomains, which resolve to 127.0.0.1, so route specifications that do not start with a leading / are taken to be subdomains of localhost. No /etc/hosts edits are involved. The README gives this example: devd ./static api=http://localhost:8888 serves a static site from localhost and proxies an app on api.localhost.
Livereload is implemented by injection. When enabled, devd inserts a small script into HTML pages just before the closing head tag. The script listens for change notifications over a websocket connection and reloads resources as needed. No browser addon is required, and the README states it works even for reverse proxied apps. The reload granularity is conditional: if only CSS changes are seen, devd reloads external CSS resources only, otherwise it does a full page reload.
The watch loop is decoupled from what is served. The -w flag watches a directory tree and triggers livereload even for files that are not being served, which is what lets you reload a reverse proxied application when its source files change. Separately, when livereload is enabled through -L, -l or -w, devd responds to SIGHUP by issuing a livereload notice to all connected browsers; if livereload is not enabled, SIGHUP makes the daemon exit. That signal behaviour is the integration point for external tools.
The repository layout matches this description: separate packages for fileserver, reverseproxy, livereload, inject, routespec, slowdown, watch and httpctx, with server.go and route.go at the top level. The go.mod lists github.com/cortesi/moddwatch for filesystem watching, github.com/gorilla/websocket for the livereload channel, and github.com/juju/ratelimit, which is consistent with the README's statement that throttling uses a token bucket implementation and handles concurrent requests.
Installing devd and serving a directory with livereload
The README gives two installation paths. The first is to download the package for your OS from the releases page and copy the binary somewhere on your PATH. The second requires a working Go installation:
# install latest release
go install github.com/cortesi/devd/cmd/devd@latest
# install latest master
go install github.com/cortesi/devd/cmd/devd@master
# copy devd binary to /usr/local/bin
cp "$(go env GOPATH)/bin/devd" /usr/local/binNote the distinction the README draws between @latest and @master. They are not the same target: the tagged release is v0.9 from 2019-01-20, while master is the branch that was last pushed on 2026-06-21. If you install @latest you get the release; if you install @master you get the current branch. The go.mod declares go 1.25.0, so building from source needs a Go toolchain at least that new.
For a first real use, the README's quick start serves the current directory, opens it in the browser with -o, and enables livereload with -l:
devd -ol .After running that, a browser window should open pointing at the daemon root, and editing a file in the directory should reload the page. The README notes that devd automatically chooses an open port unless one is specified, so you may not see a port you chose; the log output is where the actual address appears.
The second pattern from the README is the one that matters for apps that are not static files. This watches the src directory tree and reverse proxies to a locally running application:
devd -w ./src http://localhost:8080Here nothing is served from disk. The upstream at port 8080 is proxied, and a change anywhere under ./src sends a livereload notice to connected browsers. The README uses port 8888 in one example and 8080 in another; the port is whatever your application is actually listening on.
If you want TLS without configuring certificates, the README states that -s auto-generates a self-signed certificate, stores it in ~/.devd.certs, and enables TLS in one step. That is convenient for testing, and it is also a directory on your machine you should know about.
Where devd stops being the right tool
The livereload injection has a hard boundary. The README states that the closing head tag must be found within the first 30kb of the remote file, otherwise livereload is disabled for that file. For a large generated HTML document, or a proxied app that streams a long head, livereload silently will not happen. Nothing in the README describes a warning for that case.
The exclusion mechanism is pattern-based and lives on the command line. The README gives devd -x "**.less" -l . as a way to stop .less files from triggering livereload. That is fine for a handful of patterns, but there is no config file to check in, and the README presents the absence of a config file as a feature ("Designed for the terminal"). Teams that want a committed, reviewable dev-server configuration will find that a gap rather than a benefit.
The bandwidth and latency simulation is a simulation. The README is upfront that you should look up the bandwidth and latency figures yourself and convert from kilobits per second to kilobytes per second, accounting for your server's location. The example devd -d 114 -u 51 -n 275 . encodes numbers you supply, not a profile devd knows about. If you need repeatable network-condition testing across a team, the numbers have to be agreed and written down somewhere, and devd gives you no place to put them.
Finally, the release cadence is worth stating plainly. The most recent tagged release listed is v0.9 from 2019-01-20, preceded by v0.8 in 2018 and v0.7 in 2016. The repository is not archived and the last push was on 2026-06-21, so work continues on master, but the tagged releases are far apart. Anyone who pins to a release is pinning to something old.
devd against a Node-based dev server
The closest alternative in daily use is a Node-based dev server, typically a framework's own server or a livereload plugin wired into a build tool. The difference in approach is not the feature list, it is the dependency model.
A Node dev server assumes a Node runtime and a package tree on the machine, and it usually assumes your project already has a build pipeline it can hook into. In exchange you get deep integration with that pipeline, plugin ecosystems, and configuration that lives in the project. devd assumes none of that. It is a statically compiled binary with no external dependencies, per the README, and its configuration is the command line. The trade is explicit: no runtime to install, and no extension points either.
There is a middle ground worth naming, because devd's own README points at it. Modd is described as devd's sister project, a tool that runs commands and manages daemons in response to filesystem changes. The README's example modd.conf runs a render script when anything under src changes, then starts devd as a daemon over the rendered output:
src/** {
prep: render ./src ./rendered
}
rendered/*.css ./rendered/*.html {
daemon: devd -m ./rendered
}That split is the interesting part. Modd owns the build step and the process lifecycle; devd owns serving and reload. If you adopt devd, you are implicitly deciding whether you want that second tool as well, or whether a shell command in your existing watcher is enough. Note the -m flag in the example, which the README does not explain in the excerpt available here.
Maintenance, licence and the cost of upgrading
devd is MIT licensed, which is permissive and places few obligations on how you redistribute or embed it. That is a fact about the licence text, not advice about your situation; if you are shipping the binary inside a product, read the LICENSE file in the repository rather than a summary.
The maintenance picture is mixed and worth separating into two facts. The repository is not archived, and the last push was on 2026-06-21, so the branch is alive. The tagged releases, however, are v0.7 from 2016-12-07, v0.8 from 2018-01-07 and v0.9 from 2019-01-20. If your policy is to install from releases, you are installing code from 2019. If you install with go install ...@master, you are tracking a branch with no release tag behind it.
The upgrade cost follows from that split. Because there is no config file, an upgrade cannot break a checked-in configuration; the surface is the command-line flags and the route syntax. The route syntax is documented in the README in some depth, including the trailing-slash rule for subtrees and the subdomain rule for roots without a leading slash, so a route that worked before is unlikely to be ambiguous. The risk sits in the dependencies rather than in devd's own interface: go.mod pins go 1.25.0 and a set of libraries including golang.org/x/net and golang.org/x/sys, so building from master means building against whatever those resolve to at the time. Building from a release tag is the more predictable path, at the cost of running older code.
Editorial conclusion
Adopt devd if you want one binary that serves a directory, proxies a few upstreams and injects livereload without adding Node or Python to the machine, and if you are comfortable with a tool whose latest tagged release is v0.9 from 2019-01-20 while the master branch still receives pushes (the last push was on 2026-06-21). Do not adopt it if you need a documented plugin API, a config file to check into the repository, or a project with a published support policy; the README documents flags and routes, not extension points. Before committing, verify three things on your own machine: that livereload actually fires for your proxied app, since the README states the closing head tag must appear within the first 30kb of the file or livereload is disabled for that file; that the -s self-signed certificate path (~/.devd.certs) is acceptable to your browser; and that the route syntax covers your subdomain layout, because routes that do not start with a leading / are treated as subdomains of localhost.
Frequently asked questions
How do I install devd?
Download the package for your OS from the releases page and copy the binary to somewhere on your PATH, or, with a working Go installation, run go install github.com/cortesi/devd/cmd/devd@latest and copy the binary from your GOPATH bin directory. The README also documents installing the latest master with @master instead of @latest.
Does devd need a config file?
No. The README states devd is designed for the terminal, with no config file and no daemonization. Configuration is done through command-line flags and route specifications passed as arguments.
Why is livereload not working on my proxied app?
The README states that the closing head tag must be found within the first 30kb of the remote file, otherwise livereload is disabled for that file. If your proxied page has a very large head, or the tag appears later in the response, the injection will not happen.
How does devd do virtual hosting without editing /etc/hosts?
It relies on the localhost domain and all its subdomains resolving to 127.0.0.1. Route specifications that do not start with a leading slash are treated as subdomains of localhost, so api=http://localhost:8888 serves the upstream on api.localhost.
What does the -s flag do in devd?
According to the README, -s auto-generates a self-signed certificate, stores it in ~/.devd.certs and enables TLS in one step. It is a convenience for local TLS testing rather than a certificate management system.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cortesi-devd)