lns is a repo-first local reverse proxy manager for Caddy.
Each repo keeps its canonical service definition in lns.json. lns sync validates that file and compiles it into the global registry and generated Caddy config. No silent generic fallback. No opportunistic port reassignment.
Local development usually breaks down around ports:
- multiple repos want
3000 - web and backend services drift from what the repo actually expects
- Caddy or proxy config gets out of sync with the codebase
- the real source of truth ends up buried in a global registry instead of the repo
lns fixes that by making the repo own its service config and making sync explicit.
go install ./cmd/lnsYou also need Caddy installed:
# macOS
brew install caddy
# Ubuntu / Debian
sudo apt install caddy# 1. Bootstrap lns.json from the current repo
cd ~/projects/my-app
lns init
# 2. Review lns.json
# If anything is unresolved, fill it in manually or add services explicitly
lns service add api --root api --port 8000 --profile standard
# 3. Compile repo config into registry + Caddy state
lns sync
# 4. Start the proxy
lns start
# 5. Reload Caddy after later syncs
lns reloadAfter sync, services are available at:
single-service repo: http://<prefix>.localhost:8888
multi-service repo: http://<prefix>-<service>.localhost:8888
If you configure the proxy HTTP port to 80, the :8888 suffix disappears.
lns.json is the source of truth for a repo.
Example:
{
"name": "my-app",
"prefix": "my-app",
"services": {
"web": {
"root": ".",
"port": 5179,
"profile": "hmr",
"source": "detected",
"status": "resolved"
},
"api": {
"root": "api",
"port": 8000,
"profile": "standard",
"source": "manual",
"status": "resolved"
}
}
}Required top-level fields:
nameservices
Required resolved service fields:
rootportprofile
Optional service fields:
hostnamesourcestatusdockercontainer_name
Service profiles:
hmr: use proxy headers needed by HMR-style web dev serversstandard: use a normal reverse proxy block
lns init may write unresolved stubs when detection is ambiguous. lns sync refuses to compile unresolved services.
Primary workflow:
lns init
lns sync
lns status
lns reload
lns start
lns stop
lns doctor
lns service add <name> --port <port>Useful supporting commands:
# Show the repo-local view if lns.json exists in the current directory
lns status
# Force the global registry view
lns status --global
# Check whether a canonical port is already owned
lns check 5179
# Export compiled Caddy config for a synced project
lns export my-app -o Caddyfile
# Show config paths
lns config
# Interactive proxy setup (proxy port + admin address)
lns setuplns init and lns status inspect common repo signals:
package.jsonvite.config.*next.config.*nuxt.config.*vue.config.jspyproject.tomlrequirements.txtGemfile.env*
Detection is used for:
- bootstrapping
lns.json - choosing a default profile for
lns service addwhen you omit--profile - reporting drift between repo config and detectable repo signals
Detection is not used as steady-state routing truth after lns.json exists.
lns sync is a read / validate / compile step.
It:
- loads
lns.json - validates required fields
- blocks on unresolved services
- blocks on canonical port conflicts
- blocks on hostname conflicts
- updates the global registry
- regenerates the project and global Caddyfiles
It does not:
- mutate
lns.json - silently reassign ports
- silently choose a fallback framework or generic port range
If repo signals disagree with lns.json, sync reports drift but still compiles from the canonical config.
Inside a repo with lns.json, lns status shows:
- service status (
resolvedorunresolved) - source (
detected,manual, orconfig) - root
- port
- profile
- explicit hostname
- drift against detectable repo signals
Outside a repo, or with --global, lns status shows the compiled global registry view.
lns writes:
~/.lns/registry.json~/.lns/Caddyfile~/.lns/projects/*.caddy~/.lns/settings.json
The global Caddyfile imports project-level generated files. lns start and lns reload operate on that compiled state.
Compiled projects can still be exported for Docker-based use:
lns export my-app -o Caddyfile --upstream docker
lns export my-app --docker-composeFor Docker upstreams, service entries can include:
dockercontainer_name
Service unresolved after lns init:
- open
lns.json - fill in the missing
portorprofile - rerun
lns sync
Port conflict on lns sync:
- run
lns check <port> - inspect
lns status --global - change the canonical repo port in
lns.json
Drift reported by lns status or lns sync:
- either update the repo so it matches
lns.json - or update
lns.jsonand runlns syncagain
Caddy not running:
lns doctor
lns startenv GOCACHE=$PWD/.gocache GOMODCACHE=$PWD/.gomodcache go test ./...MIT