Real names
Every service gets its own hostname, so cookies, CORS, OAuth redirect URLs and subdomain routing behave the way they do in production.
open source · macOS · Linux
https://api.myapp.lokal instead of http://localhost:4000, with a certificate your browser, curl, Node and Python trust. Set it up once. After that, adding a service takes milliseconds and never asks for a password.
brew install mehmetkose/tap/lokalThen run lokal setup once.
Star mehmetkose/lokal
$ lokal setup # once; asks for your password$ lokal project create myapp # claims myapp.lokal$ lokal service add api 4000✓ api.myapp.lokal → localhost:4000 (ready in 8ms) https://api.myapp.lokal$ curl https://api.myapp.lokal # works: no -k, no --cacertwhy lokal
Every service gets its own hostname, so cookies, CORS, OAuth redirect URLs and subdomain routing behave the way they do in production.
Secure cookies, HTTP/2 and mixed-content rules work like they do in production, without -k, --insecure or clicking through browser warnings.
Adding, changing or removing a service needs no sudo, no prompts and no restarts.
Every listener is on loopback, and names are never announced on your network.
lokal's CA can only sign .lokal names (and .local, lokal's TLD before 0.6.0), never public domains or IP addresses. Even a leaked key can't impersonate a real website.
WebSockets (Vite, Next.js and webpack hot reload), server-sent events, long polling, streaming uploads and HTTP/2 all pass through.
Every command is safe to re-run, every list command has a stable --json output, and exit codes say what went wrong.
Written in Rust and licensed under Apache-2.0. Read the code, the spec and how v0.1 was built.
how it works
Creates a CA that can only sign .lokal names (and .local, lokal's TLD before 0.6.0), trusts it system-wide, points .lokal lookups at lokal's DNS server and installs a background service that runs as you. Apart from uninstalling, it's the only time lokal asks for your password.
lokal setupA project owns one domain, and each service is a subdomain routed to a port on your computer. No sudo, no prompts, no restarts. When the command returns, the URL already works.
lokal service add api 4000lokal answers *.lokal lookups with loopback, issues each name its own certificate in memory and routes by Host and SNI to localhost:<port>.
lokal open api$ lokal project create shop # https://shop.lokal; becomes the current project$ lokal service add @ 3000 # the apex itself: https://shop.lokal → localhost:3000$ lokal service add api 4000 # https://api.shop.lokal$ lokal service add v1.api 4001 # nested names work too$ lokal service add db-ui 8080 --host 127.0.0.1$ lokal run web -- npm run dev # picks a port, routes https://web.shop.lokal, starts the server$ lokal list # every project, service and URL$ lokal open api # open it in your browser$ lokal service update api --port 4002$ lokal service remove apiworks with your stack
performance
13.68ms
service add, p95
target < 300ms
0.19ms
proxy overhead, p95
target < 2ms
17.62MB
memory with 30 services
target < 30MB
104.45ms
restart → serving every name
target < 500ms
service add p95 13.68ms (< 300ms) okfirst request after add: oklookup first 1.25ms (< 20ms) oklookup repeat 0.53ms (< 5ms) okproxy overhead p95 0.19ms (< 2ms) okresumed TLS p95 1.67ms (< 10ms) oklokal list p95 6.54ms (< 30ms) oklokal status p95 6.44ms (< 30ms) okRSS with 30 services 17.62MB (< 30MB) okidle CPU over 10s 0.00% (< 0.5%) okrestart → serving all names 104.45ms (< 500ms) okall names served after restart: okMeasured by lokal's live test on macOS 27.0 with lokal 0.1.0, against the real background service. TLS is verified exactly the way macOS verifies it, and headless Chrome loads the pages. 24 of 25 checks passed; the optional Safari check was skipped.
Targets & resultsfor coding agents
Every command is safe to re-run, so an agent can repeat a step without breaking anything.
lokal list --json and lokal status --json are a stable contract ("schema": 1).
lokal --help has a section written for agents, so they can learn the workflow from the binary alone.
Agents never run lokal setup or sudo. If lokal isn’t set up, they ask you.
lokal v0.1 was itself built by a coding agent working from the spec, with human sign-off for every privileged step. Read the build log.
Add this to your repo's AGENTS.md or CLAUDE.md:
## Local HTTPS URLs (lokal)
- This repo's dev servers run behind lokal: https://web.myapp.lokal → localhost:3000,
https://api.myapp.lokal → localhost:4000.
- Set up routes with `lokal project create myapp` and `lokal service add <sub> <port>` (safe to re-run).
- Or start a dev server on a port lokal picks: `lokal run <sub> -- <dev command>`, then wait for
`✓ ready:` on stderr.
- Or, with a `lokal.toml` in the repo: run `lokal up --yes` in the background and wait for `✓ ready:` on stdout.
Stop it with Ctrl-C or SIGTERM; `lokal down` removes its routes.
- One `lokal up` runs per project. If it says `lokal up is already running`, use that stack and don't stop it;
`lokal logs <sub>` shows its output.
- To restart one server: `lokal restart <sub>`; it returns when the URL works again.
- When a .lokal URL fails, `lokal logs <sub> -n 50` shows that service's recent output (for services that
`lokal up` started).
- When a URL fails, run `lokal doctor`. Never run `lokal setup` or sudo; ask the user.faq
mkcert?mkcert creates locally trusted certificates. You still have to configure DNS (or /etc/hosts) and a server or proxy for each name. lokal does names, certificates and routing together, and its CA is name-constrained, so it can only sign .lokal names (and .local names, which lokal served before 0.6.0).
Yes, for ports you publish on your Mac. With docker run -p 8080:80 …, run lokal service add app 8080. Containers can't resolve lokal's names themselves.
Not yet. lokal listens only on loopback by design. A LAN mode is on the roadmap.
/etc/hosts?No. lokal doctor warns if /etc/hosts has an entry that conflicts with a lokal name.
.lokal, and not .test or .internal?The CA is trusted system-wide, so it is constrained to as few TLDs as possible: .lokal, which lokal serves, and .local, which it served before 0.6.0 (dropping it would mean a new CA, and a password prompt, for everyone). Adding .test or .internal would let a leaked key sign names that companies use for real internal hosts.
On macOS, once is sudo in Terminal. The second time is a macOS dialog, because macOS asks separately before it changes certificate trust settings. On Linux, setup asks once, for sudo.
Mostly: macOS 14 Sonoma or later, developed and tested on Apple Silicon; Intel builds are published but haven't been tested yet. Linux is a preview, with .deb and .rpm packages since 0.5.1, for systemd 258 or later (such as Ubuntu 26.04 or Fedora 43). Windows support can be added later.
Yes. It's open source, licensed under Apache-2.0.
get started
Install with Homebrew, then run lokal setup once. Every service after that takes one command and no password.
brew install mehmetkose/tap/lokallokal setupRequires macOS 14 Sonoma or later, or Linux with systemd 258 or later (preview). Or grab a prebuilt binary, a .deb or .rpm, or build from source.