docs / getting started

Quick start

Create a project, route your dev servers to real HTTPS names, and manage them, in five minutes.

On this page

This walkthrough takes about five minutes. It assumes you’ve installed lokal and run lokal setup. None of the commands below need sudo or ask for a password, and each URL works as soon as its command returns.

1. Create a project#

A project owns one domain. Create one per repo. The repo’s directory name makes a good project name.

terminal
$ lokal project create shop
✓ Created shop.lokal (current project)  (done in 9ms)
  Next: lokal service add api 4000

The new project becomes the current project, so the service commands below use it without any extra flags. Project names use letters, digits and -. Running project create again for a project that exists is safe: it makes that project current again.

New projects get .lokal names. To give a project a .local domain instead, pass --domain when you create it, for example lokal project create legacy --domain legacy.local. .lokal or .local? explains the difference.

2. Add services#

Each service is a subdomain routed to a port on your Mac. Start your dev server, then route a name to its port:

terminal
$ lokal service add api 4000
✓ api.shop.lokal → localhost:4000  (ready in 8ms)
  https://api.shop.lokal
$ curl https://api.shop.lokal        # works: no -k, no --cacert

If nothing is listening on the port yet, lokal still adds the route and tells you:

terminal
$ lokal service add @ 3000           # the apex itself: https://shop.lokal
✓ shop.lokal → localhost:3000  (ready in 7ms)
  https://shop.lokal
  note: nothing is listening on localhost:3000 yet

A few more forms:

terminal
$ lokal service add v1.api 4001                   # nested names work too: https://v1.api.shop.lokal
$ lokal service add db-ui 8080 --host 127.0.0.1   # an upstream host other than localhost
  • @ routes the project’s apex, https://shop.lokal.
  • Nested names such as v1.api give you https://v1.api.shop.lokal.
  • --host sends requests to a host other than localhost. You rarely need it: localhost already works whether your server listens on 127.0.0.1 or only on ::1. lokal warns if the host isn’t loopback.

Adding the same service again changes nothing. Adding an existing name with a different port fails and prints the lokal service update command to use instead.

3. See what you have#

lokal list shows every project with its services and URLs:

terminal
$ lokal list
shop  shop.lokal  (current)
  @       https://shop.lokal         → localhost:3000
  api     https://api.shop.lokal     → localhost:4000
  db-ui   https://db-ui.shop.lokal   → 127.0.0.1:8080
  v1.api  https://v1.api.shop.lokal  → localhost:4001

lokal list --json prints the same thing in a stable, machine-readable format for scripts and coding agents.

4. Open, change and remove services#

terminal
$ lokal open api                      # open https://api.shop.lokal in your default browser
✓ opened https://api.shop.lokal
$ lokal service update api --port 4002
✓ api.shop.lokal → localhost:4002  (ready in 7ms)
$ lokal service remove api
✓ Removed api.shop.lokal  (done in 6ms)

service, project show and open use the current project. Pass -p <project> to pick another one, or switch with lokal project use <project>.

5. Make Node, Python and curl trust lokal#

Browsers and macOS’s own tools, curl included, trust lokal as soon as setup finishes. Node, Python and some other tools ship their own certificate bundles. lokal env prints the exports that point them at lokal’s CA:

terminal
$ lokal env
export NODE_EXTRA_CA_CERTS='/Users/you/Library/Application Support/lokal/ca/root.pem'
export SSL_CERT_FILE='/Users/you/Library/Application Support/lokal/ca/bundle.pem'
export REQUESTS_CA_BUNDLE='/Users/you/Library/Application Support/lokal/ca/bundle.pem'
export CURL_CA_BUNDLE='/Users/you/Library/Application Support/lokal/ca/bundle.pem'

Load them into your shell before you start the tool:

terminal
$ eval "$(lokal env)"                 # zsh, bash
$ lokal env --shell fish | source     # fish
$ node -e "fetch('https://api.shop.lokal').then(r => console.log(r.status))"
$ python3 -c "import urllib.request; print(urllib.request.urlopen('https://api.shop.lokal').status)"

The bundle holds the macOS system roots plus lokal’s CA, so public HTTPS sites keep working. To make it permanent, add the eval line to your shell profile. lokal env explains each variable.

Next steps#