Latch

Self-hosting

Hosting it for other people

Hosted mode โ€” one instance, many profiles, each in its own database behind a login. What the host can and cannot see, how invites work, the admin commands, why the JSON API is off, and the open demo.

On this page

Latch is a solo game and its default install is one person, one box, no login. Hosted mode is for the other case: you run one instance and a handful of people you know each get a profile on it โ€” a beta round, a pack, a friend who will never run Docker. Nothing about the game changes. What changes is who is standing at the door.

Read this if you are a player on someone else's instance

Your log, reflections, photos and voice memos live on the host's machine, in a plain database file the host could open. Latch does not encrypt them at rest. The host also has no way to read your password (it is hashed) and no way to sign in as you without resetting it โ€” but the data itself is theirs to lose, back up, or look at. Play on a hosted instance only with someone you would hand a shoebox of photos to. You can export everything and delete your profile yourself from Account, at any time, and deletion removes the whole directory.

How it works

Turn it on with one variable:

environment:
  LATCH_HOSTED: "1"

Each profile is a directory under the data volume โ€” profiles/<id>/latch.db plus its media/ โ€” and a request only ever sees the directory of the profile whose session cookie it carries. There is no shared table with a profile_id column that a query could forget to filter on; isolation is a property of the filesystem. A small registry, hosted.db, holds handles, password hashes, invite codes and sessions, and nothing else.

Every profile's database is migrated and seeded when the instance starts, so an upgrade reaches the pup who has not signed in since โ€” and the background clock ticks every enabled profile on the same schedule it always did, so overdue training is stamped at its deadline, not at their next visit.

Say what this instance is, in your own words

  LATCH_HOSTED_NOTICE: "Beta round one. I can see whatever you submit. Everything here is destroyed on 31 October."

That sentence renders on the register page above the acknowledgement tick, and again on the sign-in page. The generic paragraph (a person, not a company; the host can read the data) always renders; this is where you say who you are, how long the instance lasts and what happens to the data at the end. Two sentences that are true beat a policy page.

Doors

Page Who
/login anyone with a handle and password
/register anyone holding an unused invite code โ€” there is no open sign-up
/guide, /healthz, /static/โ€ฆ public: the manual, the health probe, the stylesheet
everything else a signed-in profile

Sessions are a random token in an HttpOnly, SameSite=Lax cookie, stored hashed, valid 30 days and extended as you play. Changing your password signs out every other device. Eight wrong passwords in a row lock a handle for ten minutes โ€” per handle, not per address, because behind a reverse proxy every visitor can look like the proxy.

Calendar feeds work, per profile, and one rule is stricter here: a feed URL must resolve to a public address. A tester who could make the server fetch http://10.0.0.1/โ€ฆ could map the host's own network from inside it, so private, loopback and link-local destinations are refused โ€” on the URL and on every redirect. The .ics file import needs no network and is unaffected.

The JSON API is off on a hosted instance, always, whatever LATCH_API_TOKEN says. The bearer token is one global secret; on a box with many profiles it would open all of them at once. Shortcuts and the health sync are single-player features until the API grows per-profile tokens.

There is no AI Handler on a hosted instance. It talks to a model on the pup's own computer or network, and a hosted box's network is the host's, not yours โ€” so its guard refuses every call there, and its page says so.

Running it: the admin commands

There is no admin web page. A hosted beta needs an admin about as often as it needs an invite minted, and an admin login is a second thing to get right on an app that just grew its first. Everything is a command inside the container:

docker compose exec latch python -m latch.hosted invite --note "for kai"    # prints XXXX-XXXX-XXXX
docker compose exec latch python -m latch.hosted invites                    # who used which
docker compose exec latch python -m latch.hosted list                       # profiles, last seen, sessions
docker compose exec latch python -m latch.hosted disable <handle>           # out on their next click
docker compose exec latch python -m latch.hosted enable  <handle>
docker compose exec latch python -m latch.hosted passwd  <handle>           # the only "forgot my password" path
docker compose exec latch python -m latch.hosted delete  <handle> --yes     # the whole directory, gone

Invites are single-use and expire after 14 days by default (--days 0 for never). Hand the code over however you already talk to that person; the register page takes it in any case with or without the dashes.

There is deliberately no email anywhere in this: no verification, no reset links, no notifications. A mailer is an outbound call, a credential to hold, and a way for the instance to be turned into a spam relay. Password resets are you, running passwd.

What the host should also know

Demo mode

After a beta round the same instance can stay up as a trial run anyone can click around in โ€” no invite, no account, nothing kept. Turn it on with one more variable:

environment:
  LATCH_HOSTED: "1"
  LATCH_DEMO: "1"
  # optional:
  LATCH_DEMO_HOURS: "24"      # how long a demo kennel lives (1โ€“168)
  LATCH_DEMO_MAX: "40"        # demo kennels at once
  LATCH_DEMO_PER_HOUR: "30"   # new ones in any hour

A visitor with no session lands on /demo: what Latch is, three plain rules, an I am 18 or over tick and one button. Pressing it makes them a throwaway kennel โ€” a profile with a random handle (pup_3fa9c1), no password, and about five months of invented play already in it, ending now โ€” and signs them in with a cookie. Every page then carries a banner saying when it will be deleted, and Account becomes delete it now plus how to install Latch for real.

On a demo instance
/demo, /demo/start the door and its button โ€” the only way in
/login, /register, /feedback gone (they send you to /demo)
uploads refused โ€” photos, voice memos, playset and pup-file imports, all of them
calendar feeds never fetched
the JSON API, the AI Handler off, as on any hosted instance
a demo kennel deleted LATCH_DEMO_HOURS after it was made, and on every restart

Why it works this way

Each of these is a choice, and each has a reason:

What it is not

Hosted mode is players side by side, each alone in their own game. Nobody can see anyone else's clock, log or photos, including the host through the app. It is not the packs, where a Handler can act on several pups' clocks โ€” and it is not a social layer. Those remain separate designs with separate rules.


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.