-
Notifications
You must be signed in to change notification settings - Fork 0
One machine
The usual setup. One warden looks after one machine and hands out ports from a single pool.
warden serve██ ██ ██ ██ █████ ██████ ██████ ███████ ███ ██
███ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██
█████████ ██ █ ██ ███████ ██████ ██ ██ █████ ██ ██ ██
██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
█████████ ███ ███ ██ ██ ██ ██ ██████ ███████ ██ ████
█████████████████
███ █ ███ █ ███ nothing binds a port without asking
███ █ ███ █ ███
███ ███ v0.1.0 listening on http://127.0.0.1:7010
pool 8000-8999 1 reserved
It listens on 127.0.0.1:7010 and hands out 8000-8999. The registry port
itself is reserved automatically, which is the 1 reserved above.
Leave it running. Everything else talks to it.
The everyday way in, with nothing to change in the project itself:
$ warden run -- npm run dev
shop-api -> 8000
VITE ready, listening on http://localhost:8000The name comes from the directory. The process gets PORT in its environment,
holds the port while it runs, and gives it back when it exits — including when
you interrupt it.
For a run configuration in an IDE, or anything else that cannot be started by warden, ask for the same thing and print it:
$ warden env shop-api --kind backend
PORT=8000
WARDEN_PORT=8000
WARDEN_HOST=127.0.0.1
WARDEN_ADDRESS=127.0.0.1:8000
WARDEN_SERVICE=shop-apieval $(warden env shop-api --export) # into this shell
warden env shop-api --write .env # into a dotenv fileUnderneath both of those:
$ warden register shop-api --kind backend --project shop
8000The only output is the port, so it drops straight into a script:
PORT=$(warden register shop-api --kind backend)
exec ./server --port "$PORT"--kind says what the service is: backend, frontend, worker, database,
cache, proxy, or anything else you like. It is what colours the dashboard and
what --kind filters on. --project groups the services of one codebase.
$ warden ls
SERVICE KIND PROJECT ADDRESS PID
shop-api backend shop 127.0.0.1:8000 -
shop-web frontend shop 127.0.0.1:8001 -Narrow it with --project shop or --kind backend, or get machine-readable
output with --json.
A registration outlives the process that asked for it, so ask whether anyone is actually still there:
$ warden ls --holders
SERVICE KIND PROJECT ADDRESS PID HOLDER
shop-api backend shop 127.0.0.1:8000 14204 running
old-job worker - 127.0.0.1:8002 9930 gone
$ warden reap
release old-job? nothing is on 8002 and pid 9930 is gone [y/N]: y
released 1Nothing is ever taken back on its own: a service in the middle of a restart would lose its port to a timer.
$ warden history 8000
WHEN WHAT SERVICE KIND ADDRESS PID
7s ago released shop-api backend 127.0.0.1:8000 14204
2h ago registered shop-api backend 127.0.0.1:8000 14204The registry only knows the present. Every registration, renewal, move, release
and expiry is written down as it happens, so this answers for services that
were released weeks ago. warden history shop-api follows one service instead
of one port.
Instead of hardcoding a neighbour's address:
$ warden get shop-api
127.0.0.1:8000warden release shop-webTwo different wishes, two different flags:
# "3000 would be nice, but anything free will do."
warden register shop-web --kind frontend --preferred-port 3000
# "It has to be 3000, the number is baked into a config I cannot change."
warden register legacy-crm --kind backend --require-port 3000--preferred-port quietly falls back to the pool when the port is taken,
reserved or already in use. --require-port fails with a 409 instead. Both may
name a port outside the pool, which is how a legacy service on 3000 joins the
registry at all.
A service can ask for its own port as it starts:
import uvicorn
from warden import register
port = register("shop-api", kind="backend", project="shop")
uvicorn.run(app, port=port)Look a neighbour up rather than hardcoding it:
from warden import WardenClient
with WardenClient() as client:
backend = client.lookup("shop-api")
base_url = f"http://{backend.address}"Short-lived processes can hand the port back on the way out:
from warden import reserve
with reserve("test-fixture", kind="worker") as port:
run_server(port)A registration lasts until it is released. In CI or a test fixture, nothing gets the chance:
warden register ci-runner --kind worker --ttl 600The entry disappears ten minutes later unless something renews it. A heartbeat pushes the expiry out again:
warden heartbeat ci-runner --ttl 600Sent without a ttl, a heartbeat renews the lease the service registered with.
It can never turn a lease into a permanent registration by accident.
warden tuiTwo live tables, two seconds apart. tab swaps between what warden handed out
and what is actually listening.
| Key | Action |
|---|---|
↑ ↓ j k
|
Move |
tab |
Switch view |
r |
Reload now |
d |
Release the service, or stop the process |
q |
Quit |
warden serve --pool 4000-4999 --reserved 4200,4500-4510Or, so it sticks:
WARDEN_POOL_START=4000
WARDEN_POOL_END=4999
WARDEN_RESERVED=4200,4500-4510See Configuration for the full list, including a .env file.
$ warden service install
systemd user unit
~/.config/systemd/user/warden.service
...
Write it? [y/N]: y
warden starts at login - runningA systemd user unit on Linux, a launchd agent on macOS, a Startup command on
Windows — whichever this machine has. It prints the whole thing before writing
it, and warden service uninstall takes it away again.
Always as the account that ran it. A warden started by root or SYSTEM would read that account's settings and hand out ports from a registry nobody else can see.
The registry survives restarts, so services keep the ports they had.
$ warden doctor
ok warden 0.1.0 answering at http://127.0.0.1:7010, role hub
ok settings from ~/.config/warden/warden.toml
ok pool 8000-8999, 2 held, 997 free
warn 1 of 3 registrations held by something that is gone - warden reapOne command instead of four: whether a warden is there, where its settings came
from, whether it is open to the network without a token, how much pool is left,
whether anything is held by a process that is not, whether the nodes are still
reporting, and whether there is a newer version. It exits 1 only when
something failed, so it drops straight into a health check.
warden — nothing binds a port without asking ·
uv tool install warden-ports
Repository · Issues · Releases · PyPI · MIT
Getting started
While it runs
Several machines
Reference