A ReadyRoam server lets the app sync your trips across devices, share trips with the people you travel with, and keep an encrypted backup of your personal details. It’s a single Docker image with a Postgres database.
What you need
- A computer that’s always on, with Docker and Docker Compose: a home server, a NAS, a Raspberry Pi 4 or 5, or a small cloud server. The image runs on both amd64 and arm64.
- A domain name pointing at it (for example
roam.example.com), and HTTPS in front of the server. The app only connects to servers over HTTPS, apart from bare IP addresses on an iPhone, which is only useful fortrying it out at home.
1. Get the files
Make a folder, for example readyroam, and put two files in it.
docker-compose.yml
services:
server:
image: ghcr.io/aengeln/readyroam:${READYROAM_VERSION:-latest}
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgresql://readyroam:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@db:5432/readyroam?sslmode=disable
TOKEN_SECRET: ${TOKEN_SECRET:?set TOKEN_SECRET in .env}
PUBLIC_URL: ${PUBLIC_URL:-}
SERVER_NAME: ${SERVER_NAME:-ReadyRoam}
REGISTRATION: ${REGISTRATION:-closed}
ACCOUNT_DELETION: ${ACCOUNT_DELETION:-true}
TRUST_PROXY: ${TRUST_PROXY:-false}
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-}
SMTP_SECURITY: ${SMTP_SECURITY:-}
SMTP_USERNAME: ${SMTP_USERNAME:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM: ${SMTP_FROM:-}
ports:
# Only reachable from this computer; the reverse proxy (step 4) passes
# requests on. Use "8080:8080" to reach it from the network instead.
- "127.0.0.1:${PORT:-8080}:8080"
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: readyroam
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_DB: readyroam
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U readyroam -d readyroam"]
interval: 5s
timeout: 5s
retries: 10
volumes:
db-data:
.env
# The database password. Pick a long random one; you won't need to type it.
POSTGRES_PASSWORD=
# Signs sign-in tokens. At least 32 characters, for example the output of:
# openssl rand -base64 48
# Changing it signs everyone out.
TOKEN_SECRET=
# The address people reach the server at. Invite links point here.
PUBLIC_URL=https://roam.example.com
# The name the app shows for this server.
SERVER_NAME=ReadyRoam
# Who may create an account in the app: closed (you create accounts, see
# step 5) or open (anyone who can reach the server).
REGISTRATION=closed
# Whether people may delete their own account in the app.
ACCOUNT_DELETION=true
# true when a reverse proxy is in front of the server (step 4).
TRUST_PROXY=true
# The image version: latest, a major version such as 1 (new features, nothing
# that breaks), a minor version such as 1.0 (bug fixes only), or an exact
# version such as 1.0.0.
READYROAM_VERSION=latest
2. Fill in the secrets
Set POSTGRES_PASSWORD and TOKEN_SECRET in .env. Both can be generatedwith:
openssl rand -base64 48
Set PUBLIC_URL to your server’s address.
3. Start it
docker compose up -d
curl http://localhost:8080/healthz
The answer should be {"status":"ok"}. The database is set up the firsttime the server starts.
4. Put HTTPS in front
The server speaks plain HTTP, so put a reverse proxy in front of it that handles HTTPS. Caddy is the simplest: it gets and renews certificates by itself. Install it on the same computer, and use thisCaddyfile:
roam.example.com {
reverse_proxy localhost:8080
}
Traefik and nginx work too. Whichever you use, keep TRUST_PROXY=true in .env, so the server sees each visitor’s own address (it uses it to slowdown password guessing). Without a reverse proxy, set it to false.
5. Create accounts
New servers don’t let people sign up in the app. Create accounts yourself with the admin tool:
docker compose exec server /app/bin/server admin create-user anna@example.com
It prints a random password (or give one after the email address). Then, int he app: Settings → Server and sync → Sign in, with your server’s address, the email and the password.
Other admin commands:
docker compose exec server /app/bin/server admin users
docker compose exec server /app/bin/server admin reset-password anna@example.com
docker compose exec server /app/bin/server admin sign-out anna@example.com
docker compose exec server /app/bin/server admin disable anna@example.com
docker compose exec server /app/bin/server admin enable anna@example.com
To let anyone sign up in the app instead, set REGISTRATION=open and run docker compose up -d again.
Sharing trips
Anyone with an account can share a trip from the app. The people they invite open the link and join. They don’t need an account: the invite lets their phone in for that trip only. Invite links point at PUBLIC_URL, so make sure it’s set.
Personal backups and recovery keys
People’s personal details (passports, visas, vaccinations, loyalty programs, profile photos and the like) are backed up end-to-end encrypted. The server only stores data it can’t read. When someone turns on the backup, the app shows a recovery key, which they need to unlock the backup on a new phone.
As the admin, you can’t recover it for them. Resetting someone’s password lets them sign in again, but if they lose both their phones and their recovery key, their personal backup is gone. Trips aren’t affected: they’re not end-to-end encrypted, so they sync to a new phone after signing in.
Password reset by email
Without email, you reset passwords with the admin tool (step 5). To let people reset their own password from the app, add a mail server to .env:
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURITY=starttls # starttls (587), tls (465) or none
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_FROM=ReadyRoam <roam@example.com>
PUBLIC_URL must be set too: the email links to a page on your server where
the new password is chosen.
Backups
Everything, including tickets and other files kept with trips, is in the
Postgres database. Back it up regularly, and always before upgrading:
docker compose exec -T db pg_dump -U readyroam readyroam | gzip > readyroam-$(date +%F).sql.gz
To restore a backup into an empty database:
docker compose down
docker volume rm readyroam_db-data # the volume is named after the folder
docker compose up -d db
gunzip -c readyroam-2026-10-04.sql.gz | docker compose exec -T db psql -U readyroam readyroam
docker compose up -d
Upgrading
docker compose exec -T db pg_dump -U readyroam readyroam | gzip > before-upgrade.sql.gz
docker compose pull
docker compose up -d
The database is updated when the new version starts. This can’t be undone: going back to an older version needs the backup you made first.
To decide yourself when to upgrade, pin a version in .env:READYROAM_VERSION=1 gets new features but nothing that breaks, andREADYROAM_VERSION=1.0 gets bug fixes only.
For companies
A company that runs its own server for its staff will usually want:
REGISTRATION=closed # accounts are created by the company
ACCOUNT_DELETION=false # people can't delete their account themselves
With ACCOUNT_DELETION=false, the app doesn’t offer account deletion.
Limits
- Each file kept with a trip (a ticket, a boarding pass) can be up to
20 MB, and a trip’s files up to 500 MB together. - Encrypted personal files (photos of passports and the like) can be up to
20 MB each. - Too many failed sign-ins from one address (10 within 5 minutes) block that
address from signing in for 5 minutes. So do more than 5 sign-ups or
password reset emails within 5 minutes.
Settings
| Setting | Default | |
|---|---|---|
POSTGRES_PASSWORD | (required) | The database password. |
TOKEN_SECRET | (required) | Signs sign-in tokens; 32 characters or more. |
PUBLIC_URL | The address people reach the server at. Needed for invite links and password reset. | |
SERVER_NAME | ReadyRoam | Shown in the app. |
REGISTRATION | closed | closed or open. |
ACCOUNT_DELETION | true | Whether people may delete their own account in the app. |
TRUST_PROXY | false | Set to true behind a reverse proxy. |
PORT | 8080 | The port on this computer. |
READYROAM_VERSION | latest | The image version to run. |
SMTP_HOST, SMTP_PORT, SMTP_SECURITY, SMTP_USERNAME, SMTP_PASSWORD, SMTP_FROM | Email for password reset. |
Troubleshooting
- The app can’t reach the server. Check that
https://your-address/healthz
answers{"status":"ok"}in a browser. The app needs HTTPS. - “This server doesn’t take sign-ups in the app.” Registration is
closed; create the account with the admin tool, or setREGISTRATION=open. - “Too many attempts.” Wait five minutes. If it happens to everyone at
once, the server is probably behind a reverse proxy withoutTRUST_PROXY=true, so it sees everyone as the same visitor. - Logs:
docker compose logs -f server.