---
title: "Deploy FMD Server | (Just Updated) Self-Hosted Find My Device Backend, Signups Locked, Data Survives Redeploys"
description: "FMD Server, self-hosted Find My Device backend, token-gated signup"
category: "Other"
url: https://railway.com/deploy/fmd-server-or-just-updated-self-hosted-f
---

# 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 on Railway](https://railway.com/template/fmd-server-or-just-updated-self-hosted-f)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/fmd-server-or-just-updated-self-hosted-f/manifest.json

- **Creator:** SuperSlowSloth
- **Category:** Other

## Template content

### fmd-server

- **Image:** ghcr.io/bon5co/fmd-server-railway:0.17.1@sha256:8455e736dbb7094686ba0300977a53b53d9c1ed4eb9b518b4c30290a99814b0e
- **Health check:** /version
- **Public domain:** Yes

## Documentation

# 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

```bash
#!/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

- [FMD Server repository](https://gitlab.com/fmd-foss/fmd-server)
- [FMD project site](https://fmd-foss.org/)
- [Wrapper image source](https://github.com/bon5co/fmd-server-railway)


## Similar templates

- [Rocky Linux](https://railway.com/deploy/rocky-linux) — Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀
- [Foundry Virtual Tabletop](https://railway.com/deploy/X5tR6G) — A Self-Hosted & Modern Roleplaying Platform
- [Letta Code Remote](https://railway.com/deploy/letta-code-remote) — Run a Letta Code agent 24/7. No inbound ports, just deploy.

Open this page in a browser: https://railway.com/deploy/fmd-server-or-just-updated-self-hosted-f
