FEATURES

Domain names

Every running container has a name you can open in the browser: http://web.kiri.local for a container called web. You don’t publish a port or remember which one you picked. Kiri finds the container from the name and forwards the request to it.

Two kinds of name

Kiri answers on two suffixes. They reach the same containers in the same way.

Name Setup How the Mac finds it
<name>.kiri.localhost None. Works from the start. macOS resolves every *.localhost name to this Mac by itself.
<name>.kiri.local Turned on once, with an administrator password. A resolver file sends *.kiri.local lookups to Kiri.

The window and the router’s own pages use whichever suffix is in use: kiri.local once it is turned on, kiri.localhost until then.

docker run -d --name web nginx
open http://web.kiri.localhost   # always works
open http://web.kiri.local       # once .kiri.local is on

Turning on .kiri.local

The first time you open Kiri, it asks how you want to reach your containers: by port on localhost, or by name on .kiri.local. Pick .kiri.local and macOS asks for an administrator password. You can change your answer later in Settings → Domains → Local names.

Kiri
ContainersImagesVolumesNetworksSettings

Domains

Local names<name>.kiri.localkiri.localhostkiri.local
HTTPShttps://myapp.kiri.localOffOn
Compose services answer at <service>.<project>.kiri.local
Settings → Domains. Local names picks the suffix; switching to kiri.local asks macOS for a password, once.

Turning it on changes one thing on your Mac: Kiri writes /etc/resolver/kiri.local, which tells macOS to send lookups for kiri.local and every name under it to 127.0.0.1, port 53535:

# Kiri: container names (kiri setup --uninstall removes this)
nameserver 127.0.0.1
port 53535

Writing to /etc/resolver needs an administrator, which is why macOS asks. Kiri never sees the password. Nothing else needs root: the DNS server on port 53535 and the router on ports 80 and 443 run as you. Turning it off in Settings removes the file again (another password prompt), and containers go back to .kiri.localhost.

From the command line, kiri setup --domains does the same as turning it on.

How a name reaches a container

Kiri’s DNS server answers every name under kiri.local with 127.0.0.1, and refuses everything else. The browser then connects to port 80 (or 443 for HTTPS) on this Mac, where Kiri’s router reads the Host header, finds the running container with that name and relays the connection into the VM to the container’s own address.

Your MacKiri’s VMmacOS resolver/etc/resolver/kiri.localKiri DNS127.0.0.1:53535UDP127.0.0.1Browserweb.kiri.locallookupKiri’s router:80 :443127.0.0.1:80Host: web.kiri.localguestagent relayweb:80vsockTCP
A request for web.kiri.local: the lookup goes to Kiri's DNS, which answers 127.0.0.1; the browser connects to Kiri's router, which picks the container from the Host header and relays to it over vsock.

Because the router connects to the container’s address inside the VM, a container doesn’t need -p to have a name. Publishing a port is only for reaching it on localhost:<port>.

If no running container has the name, the router answers with a page listing the containers that are running, as links. The router listens on every interface but serves only connections from this Mac; other machines on your network get nothing.

Which names a container gets

A running container answers to each of these, with either suffix:

Name Example
Its container name web.kiri.local
Compose: <service>.<project> api.shop.kiri.local for service api in project shop
Each name in its dev.kiri.domain label api.myapp.kiri.local
Its ID, 12 characters or more 3f2a9c1e7b04.kiri.local

The dev.kiri.domain label picks names of your own. Separate several with commas; the first is the one the window shows. You can write a name with or without the suffix.

docker run -d --name web-1 --label dev.kiri.domain=api.myapp,api.test nginx
open http://api.myapp.kiri.local

In a Compose file, <service>.<project> needs nothing extra. The project is the folder name unless you set one:

services:
  api:
    image: nginx
    labels:
      dev.kiri.domain: api.myapp

With the project shop, that service answers at both api.shop.kiri.local and api.myapp.kiri.local. See Compose for the rest.

Which port it uses

A name has no port in it, so Kiri picks the container port to send the request to. It goes down this list and stops at the first that applies:

dev.kiri.http.port label set?Exactly one TCP port exposed?One of 80, 8080, 3000, 8000, 5000 exposed?Otherwiseyesyesyesthat portthat portthe lowest of themport 80nonono
How the router picks a container's HTTP port. The exposed ports are the image's EXPOSE lines plus --expose.

An app that listens on a port the image doesn’t declare, say 4000, needs the label:

docker run -d --name docs --label dev.kiri.http.port=4000 my-docs-image

A container on --network host is reached at that port on the VM itself.

© 2026 Kiri · runkiri.com