Deploy FMD Server | (Just Updated) Self-Hosted Find My Device Backend, Signups Locked, Data Survives Redeploys

FMD Server, self-hosted Find My Device backend, token-gated signup

Deploy FMD Server | (Just Updated) Self-Hosted Find My Device Backend, Signups Locked, Data Survives Redeploys

Just deployed

/var/lib/fmd-server/db

Deploy and Host FMD Server on Railway

Built for an LLM agent to deploy and use. The deploy form has no fields to fill: signups are locked behind a generated registration token, the server binds Railway's port, and the database sits on a volume. FMD Server is the open-source, self-hosted server for FMD (Find My Device), the app that locates, rings, locks and wipes your own Android phone without handing its location to a third party. The server stores only client-side-encrypted location points, pictures and commands.

About Hosting FMD Server

This template runs one service, fmd-server (the server and its web client on one public domain, with a volume at /var/lib/fmd-server/db holding the SQLite database), so accounts, saved locations and pending commands survive redeploys.

Locked down from boot. Upstream ships with open registration: with an empty registration token, anyone who finds the domain can create accounts and store data on your server. This template always sets FMD_REGISTRATIONTOKEN and the wrapper refuses to start without one, so a new account needs the token. The server has no administrator account: each user registers from the FMD app or the web client with the token, and the password never reaches the server in a form it can read (the client sends a hash and an encrypted master key). The wrapper also binds Railway's injected PORT, which upstream ignores (it listens on 8080 from its own config), and fixes the root-owned volume: the server runs as uid 1000 with a private database directory, so the entrypoint takes ownership of the mount as root and then drops privileges. The log prints the owner before and after.

Using it. Open https://YOUR_DOMAIN for the web client, or enter that URL as the server in the FMD app, and register with FMD_REGISTRATIONTOKEN. Hand the token only to people who should have an account. TLS is terminated by Railway, so the server's own TLS listener is off. Upstream is FMD Server v0.17.1; the Prometheus metrics endpoint listens on loopback only and is not exposed.

Agent contract

  • Public URL: https://RAILWAY_PUBLIC_DOMAIN (the generated domain of the fmd-server service; the REST API is under /api/v2)
  • Private URL: http://fmd-server.railway.internal:8080
  • Credentials: the variable FMD_REGISTRATIONTOKEN on the fmd-server service; accounts have no server-side password, each client chooses its own username and password hash
  • Auth: POST /api/v2/account/register with {"username","salt64","passwordHash64","protoVersion":2,"encMasterKey64","registrationToken":"$FMD_REGISTRATIONTOKEN"} returns {"accessToken"}; POST /api/v2/account/login with {"username","passwordHash64","sessionDurationSeconds"} returns a new one. Send it as Authorization: Bearer $TOKEN. A write with a wrong token answers 401, and a request with no Authorization header answers 400. A registration with a wrong token answers 401 and a login with a wrong hash answers 403.
  • Health: GET /version returns 200
#!/bin/sh
# needs: RAILWAY_PUBLIC_DOMAIN, FMD_REGISTRATIONTOKEN (variables of the fmd-server service); also curl. Creates one throwaway account.
B="https://$RAILWAY_PUBLIC_DOMAIN/api/v2"
J="Content-Type: application/json"
U="agent-$(date +%s)"
H="aGFzaA=="
curl -s -o /dev/null -w "health: %{http_code}\n" "https://$RAILWAY_PUBLIC_DOMAIN/version"
# the server stores only client-side-encrypted blobs, so the salt, hash and master key are opaque base64 the client chooses
TOKEN=$(curl -s -X POST -H "$J" -d "{\"username\":\"$U\",\"salt64\":\"c2FsdA==\",\"passwordHash64\":\"$H\",\"protoVersion\":2,\"encMasterKey64\":\"a2V5\",\"registrationToken\":\"$FMD_REGISTRATIONTOKEN\"}" "$B/account/register" | sed -n 's/.*"accessToken":"\([^"]*\)".*/\1/p')
if [ -n "$TOKEN" ]; then echo "register: ok"; else echo "register: FAILED"; fi
LOGIN=$(curl -s -X POST -H "$J" -d "{\"username\":\"$U\",\"passwordHash64\":\"$H\",\"sessionDurationSeconds\":3600}" "$B/account/login" | sed -n 's/.*"accessToken":"\([^"]*\)".*/\1/p')
if [ -n "$LOGIN" ]; then echo "login: ok"; else echo "login: FAILED"; fi
D='{"items":[{"clientItemIdHex":"00112233445566778899aabbccddeeff","unixMillis":1,"ciphertext64":"Y2lwaGVy"}]}'
curl -s -o /dev/null -w "write with token: %{http_code}\n" -X POST -H "$J" -H "Authorization: Bearer $LOGIN" -d "$D" "$B/data/location"
curl -s -o /dev/null -w "write with wrong key: %{http_code}\n" -X POST -H "$J" -H "Authorization: Bearer ${LOGIN}x" -d "$D" "$B/data/location"
curl -s -o /dev/null -w "write without header: %{http_code}\n" -X POST -H "$J" -d "$D" "$B/data/location"
curl -s -o /dev/null -w "read with wrong key: %{http_code}\n" -H "Authorization: Bearer ${LOGIN}x" "$B/data/location"
curl -s -o /dev/null -w "read with token: %{http_code}\n" -H "Authorization: Bearer $LOGIN" "$B/data/location"
curl -s -o /dev/null -w "register with wrong token: %{http_code}\n" -X POST -H "$J" -d "{\"username\":\"intruder-$U\",\"salt64\":\"c2FsdA==\",\"passwordHash64\":\"$H\",\"protoVersion\":2,\"encMasterKey64\":\"a2V5\",\"registrationToken\":\"wrong\"}" "$B/account/register"
curl -s -o /dev/null -w "login with wrong hash: %{http_code}\n" -X POST -H "$J" -d "{\"username\":\"$U\",\"passwordHash64\":\"d3Jvbmc=\",\"sessionDurationSeconds\":3600}" "$B/account/login"

A fresh deploy prints health: 200, register: ok, login: ok, write with token: 200, write with wrong key: 401, write without header: 400, read with wrong key: 401, read with token: 200, register with wrong token: 401 and login with wrong hash: 403.

Why Deploy FMD Server on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying FMD Server on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.

  • Agent-ready - no deployer fields, a contract block and one script that proves registration, login, a write, the refusal of a write with a wrong token and the refusal of a signup with a wrong registration token.
  • Signups locked - open registration is replaced by a generated token.
  • Data survives redeploys - the SQLite database sits on a volume the entrypoint makes writable for the unprivileged server user.
  • Pinned - the upstream image is pinned by digest and the wrapper by tag and digest.
  • Your location stays yours - the server holds only ciphertext it cannot read, on a domain you control.

Common Use Cases

  • Find your own phone - locate, ring, lock or wipe an Android phone from the FMD web client, without a Google account in the loop.
  • Family or team devices - one server, one registration token, an account per person.
  • Privacy-first Find My Device - replace a cloud locator that sees your location history with one that stores only ciphertext.
  • Agent-driven device tooling - give an agent an account to queue commands and read encrypted location points over the REST API.

Dependencies for FMD Server Hosting

  • The fmd-server service with its volume, created by the template. A custom domain is optional.

Deployment Dependencies


Template Content

More templates in this category

View Template
Rocky Linux
Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀

codestorm
51
View Template
Foundry Virtual Tabletop
A Self-Hosted & Modern Roleplaying Platform

Lucas
71
View Template
Letta Code Remote
Run a Letta Code agent 24/7. No inbound ports, just deploy.

Letta
52