open source · macOS

Real HTTPS domains for your local dev servers

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/lokal
  • macOS 14+
  • Loopback only
  • No sudo after setup
https://docs.myapp.lokal
https://web.myapp.lokal
https://console.myapp.lokal
https://backoffice.myapp.lokal
https://api.myapp.lokal
~/code/myapp — zsh
$ 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 --cacert

why lokal

Your dev servers, behaving like production

Real names

Every service gets its own hostname, so cookies, CORS, OAuth redirect URLs and subdomain routing behave the way they do in production.

Valid HTTPS

Secure cookies, HTTP/2 and mixed-content rules work like they do in production, without -k, --insecure or clicking through browser warnings.

Fast and quiet

Adding, changing or removing a service needs no sudo, no prompts and no restarts.

Nothing leaves your Mac

Every listener is on loopback, and names are never announced on your network.

A narrowly scoped CA

lokal's CA can only sign .lokal and .local names, never public domains or IP addresses. Even a leaked key can't impersonate a real website.

Your whole dev stack

WebSockets (Vite, Next.js and webpack hot reload), server-sent events, long polling, streaming uploads and HTTP/2 all pass through.

Scriptable

Every command is safe to re-run, every list command has a stable --json output, and exit codes say what went wrong.

Open source

Written in Rust and licensed under Apache-2.0. Read the code, the spec and how v0.1 was built.

Real namesValid HTTPSNo sudoNo restartsLoopback onlyHTTP/2Hot reload

how it works

Set up once. Then every service is one command.

  1. 01

    Set up once

    Creates a CA that can only sign .lokal and .local names, trusts it in the System keychain, 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 setup
  2. 02

    Add a service

    A project owns one domain, and each service is a subdomain routed to a port on your Mac. No sudo, no prompts, no restarts. When the command returns, the URL already works.

    lokal service add api 4000
  3. 03

    Open it

    lokal 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
~/code/shop — zsh
$ 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 list                     # every project, service and URL$ lokal open api                 # open it in your browser$ lokal service update api --port 4002$ lokal service remove api

works with your stack

Hot reload, WebSockets, HTTP/2. It all passes through.

Vite

Vite rejects Host headers it doesn’t recognise. Allow your lokal names, or have lokal send Host: localhost:<port> upstream.

Next.js & webpack

Hot reload runs over WebSockets, and WebSockets pass straight through the proxy.

Node & Python

They ship their own certificate bundles. lokal env sets NODE_EXTRA_CA_CERTS, SSL_CERT_FILE and REQUESTS_CA_BUNDLE, and public sites keep working.

OAuth & cookies

Each service has its own hostname, so redirect URLs, CORS and cookies behave like production instead of sharing localhost across ports.

curl

macOS’s own curl trusts lokal as soon as setup finishes: no -k, no --cacert. For other curl builds, lokal env sets CURL_CA_BUNDLE.

Docker

Works for ports you publish on your Mac. Containers can’t resolve lokal’s names themselves.

IPv6-only servers

localhost upstreams work whether your server listens on 127.0.0.1 or only on ::1. Pin one with --host.

Streaming

Server-sent events, long polling, streaming uploads and HTTP/2 all pass through.

performance

Fast enough that you never wait for it

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

live test · L23 Performance
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: ok

Measured 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 & results

for coding agents

Your agent can drive it, you keep the password

  • 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:

AGENTS.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).
- When a URL fails, run `lokal doctor`. Never run `lokal setup` or sudo; ask the user.

Exit codes say what went wrong

0
Success
1
Runtime error, or doctor found a failure
2
Usage error
3
Setup hasn't run
4
Needs sudo, but sudo isn’t possible

faq

Questions, answered

How is this different from 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 and .local names.

Does it work with Docker?

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.

Can I open my dev URLs on my phone or another computer?

Not yet. lokal listens only on loopback by design. A LAN mode is on the roadmap.

Does it edit /etc/hosts?

No. lokal doctor warns if /etc/hosts has an entry that conflicts with a lokal name.

Why only .lokal and .local, and not .test or .internal?

The CA is trusted system-wide, so it is constrained to exactly the TLDs lokal serves. Adding .test or .internal would let a leaked key sign names that companies use for real internal hosts.

Why does setup ask for my password twice?

Once is sudo in Terminal. The second time is a macOS dialog, because macOS asks separately before it changes certificate trust settings.

Is it macOS only?

For now, yes: macOS 14 Sonoma or later. It's developed and tested on Apple Silicon; Intel builds are published but haven't been tested yet. The code is structured so Linux and Windows support can be added later.

Is lokal free?

Yes. It's open source, licensed under Apache-2.0.

More answers in the docs

get started

brew install. lokal setup. Done.

Install with Homebrew, then run lokal setup once. Every service after that takes one command and no password.

brew install mehmetkose/tap/lokal
lokal setup

Requires macOS 14 Sonoma or later. Or grab a prebuilt binary, or build from source.