Self-hosting
Install and self-host
Docker Compose in five minutes, the three environment variables, why a reverse proxy with authentication is not optional, HTTPS for the camera and microphone, updating, and a bare-metal dev run.
On this page
Latch is one container, one volume, no database server. It is meant to live on the box in your closet, on your LAN, behind your own front door.
Requirements
- Docker with Compose (any recent version), on any Linux host β a Raspberry Pi is plenty.
- A reverse proxy that can add authentication and HTTPS (Caddy, Traefik, nginx, Nginx Proxy Managerβ¦), or a private network (Tailscale, NetBird, WireGuard) that already does.
- About 200 MB for the image; storage for your photos and voice memos.
Five-minute install
git clone <the repository> latch && cd latch
cp .env.example .env
# set LATCH_API_TOKEN if you want the API; leave it empty to keep the API off
docker compose build --pull
docker compose up -d
curl -s http://127.0.0.1:8770/healthz
/healthz answers with the version, the session state and the loaded modules. The image is
built locally from the Dockerfile; there is no registry pull yet
(roadmap β releases).
The shipped docker-compose.yml publishes port 8770 on the host, mounts a named volume at
/data, runs read-only with a 512 MB limit, and sets TZ/LATCH_TZ. Change the host port or
the timezone to taste.
The environment
| Variable | What | Default |
|---|---|---|
LATCH_API_TOKEN |
bearer token for /api/v1. Empty = API disabled (503), on purpose. Generate with python3 -c 'import secrets;print(secrets.token_urlsafe(32))' and keep it in your password manager. |
(empty) |
LATCH_TZ |
your local timezone (IANA name) | America/Detroit |
LATCH_APP_NAME |
display name | Latch |
More in Customise β instance settings.
Authentication is yours
Latch has no login of its own
Anyone who can reach the port can play your game and read your journal. The port must be reachable only from your reverse proxy (or your private network), and the proxy must ask for a login. This is the one piece of setup that is not optional.
Two patterns that work:
A. Reverse proxy with forward-auth. Caddy + tinyauth/Authelia/Authentik, Traefik with a
forward-auth middleware, nginx with auth_request. Example for Caddy with tinyauth:
latch.home.example {
tls internal
forward_auth tinyauth:3000 {
uri /api/auth/caddy
copy_headers Remote-User
}
reverse_proxy latch-host:8770
}
Then make sure 8770 is not reachable from anything but the proxy β a firewall rule on
the host, or bind the port to the proxy's address only. Note that Docker's published ports
bypass host firewalls such as UFW; use DOCKER-USER rules or bind to 127.0.0.1 if the
proxy is on the same host.
B. Private network only. If the host is reachable only over Tailscale/NetBird/WireGuard and every device on that network is yours, the network is the login. Still terminate HTTPS somewhere, for the next section.
HTTPS is required
Not for privacy β the traffic never leaves your LAN β but because browsers refuse three
things on an insecure origin: the microphone (spoken reflections), the camera on some
phones, and the share sheet. All three work at https://latch.your.lan through your proxy
and are structurally absent at http://host:8770. A proxy-issued internal certificate (Caddy's
tls internal, or your own CA) is fine; install its root on your phone once.
Updating
git pull
docker compose build --pull
docker compose up -d
curl -s http://127.0.0.1:8770/healthz # the version must read back as the one you built
docker compose pull does nothing here β the image is local. Read the version off
/healthz rather than trusting the exit code. The changelog is CHANGELOG.md; schema
migrations run on start and are recorded in the database.
If you keep a rollback: docker tag aurora/latch:local aurora/latch:rollback-$(date +%F)
before the build.
Backups
One volume: the database (WAL mode) and media/. See
Export and backup for a WAL-safe copy and the watermark
that proves it.
Running on a workstation (development)
uv venv --python 3.12 .venv
uv pip install --python .venv/bin/python -r requirements.txt pytest httpx
./run-dev.sh # http://127.0.0.1:8770, data in ./.data, API token "dev-token"
.venv/bin/pytest -q # the test suite, including the privacy and settings-split guards
.data/ is throwaway and gitignored. The dev server and a container share nothing.
A worked example
The reference deployment runs on a Proxmox VM behind Caddy (tls internal, tinyauth β
passkeys), with the raw port firewalled to the proxy host and to the private mesh, a
keyword health monitor on /healthz, a nightly WAL-safe database dump, and the whole VM in
two backup layers. None of that is required; all of it is the shape the
privacy page describes.
This page also ships inside the app, at /guide β so your own instance always serves the guide for the version you are running, with the internet unplugged. Get Latch Β· Something wrong here? Tell me.