Self-hosting Altair

This guide walks through running Altair on your own hardware. By the end you will have the server, web client, and database running on a single machine, ready to connect a mobile client.

Altair is designed to run on modest hardware. A Raspberry Pi 4 with 4GB of RAM is enough for one or two users. A Pi 5 or an 8GB VPS is more comfortable for a household of 3–5.

Hardware requirements

Minimum (1–2 users)

  • 4GB RAM (Raspberry Pi 4, or a $5/month VPS)
  • 4-core ARM64 or x86_64 CPU
  • 32GB of storage, plus external storage for attachments
  • Docker and Docker Compose v2

Recommended (3–5 users)

  • 8GB RAM (Raspberry Pi 5, a NAS, or a $15/month VPS)
  • 64GB+ of storage

The attachment store can live on external storage without any performance penalty — that’s what it’s designed for.

Prerequisites

You’ll need:

  • Docker and Docker Compose v2. On Debian/Ubuntu, sudo apt install docker.io docker-compose-v2 and add your user to the docker group.
  • mise — manages the Bun, Rust, and Java toolchains for development. Not strictly required for a pure deployment, but strongly recommended if you plan to build from source.
  • sqlx CLI — for running database migrations. Install with cargo install sqlx-cli --no-default-features --features postgres.

Clone the repository

git clone https://github.com/getaltair/altair.git
cd altair

Configure secrets

Altair ships with a .env.example file that documents every environment variable the stack needs. Copy it and fill in your own values:

cp infra/compose/.env.example infra/compose/.env

Open infra/compose/.env and set:

  • POSTGRES_PASSWORD — a strong password for the Postgres user.
  • JWT_PRIVATE_KEY — the signing key for authentication tokens. Generate a PEM RSA key with openssl genrsa 2048 | base64 -w0.
  • RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY — credentials for the RustFS storage container.
  • S3_ACCESS_KEY and S3_SECRET_KEY — credentials the Altair server uses to connect to RustFS. These must match the RUSTFS_* values above.
  • DATABASE_URL — update to match your Postgres credentials.

Keep this file out of git. .env contains secrets. The default .gitignore already excludes it, but double-check before pushing.

Install toolchains (optional, for building from source)

If you plan to build the server locally instead of using the published image:

mise install    # Bun, Rust stable, Java 17 (temurin)
cargo install sqlx-cli --no-default-features --features postgres

Start infrastructure

cd infra/compose
docker compose up -d

This brings up:

  • Postgres — the source of truth for all data
  • PowerSync — the sync service for client data
  • RustFS — an S3-compatible object store for attachments

The Altair server binary is not included in the compose stack. Start it separately as described in the Start the clients section below.

Verify everything is healthy:

docker compose ps

Every service should report healthy or running.

Run database migrations

From the repository root, with DATABASE_URL matching the values in infra/compose/.env:

DATABASE_URL=postgres://altair_user:yourpassword@localhost:5432/altair_db 
  sqlx migrate run --source infra/migrations

Migrations are idempotent — running twice is safe and fast. If a migration fails, the database is left in the pre-migration state and the error is printed.

Apply seed data (optional)

Useful for a first run, especially if you want to poke around before committing any real data:

psql $DATABASE_URL -f infra/scripts/seed.sql

Skip this in production.

Start the clients

In development mode, you typically run each client in its own terminal.

Server (for local builds; skip if using the Docker image)

cd apps/server
DATABASE_URL=postgres://altair_user:yourpassword@localhost:5432/altair_db cargo run

The API listens on port 8000 by default.

Web client

cd apps/web
bun install
bun dev

The web client listens on port 5173 and proxies /api to the server on 8000.

Android client

Open apps/android/ in Android Studio and run on an emulator or a connected device. The mobile client connects to your server over the network, so make sure the device can reach the server’s address.

Create the first user

The first account created on a fresh Altair install is automatically granted admin rights. Open the web client at http://localhost:5173, register an account, and you’re in.

After that, new accounts are regular users by default. You can promote other users from the admin interface once you’re logged in.

Plan your admin account before you expose the server to the network. The “first user becomes admin” rule is intentional and documented in ADR-013, but it means that until you’ve created that account, anyone who can reach the signup endpoint can claim admin.

Backups

Altair’s source of truth is Postgres plus the RustFS attachment store. A reliable backup plan covers both:

  • Database — a nightly pg_dump written to a location outside the host. Any standard Postgres backup strategy works; the database is an ordinary Postgres instance.
  • Attachments — back up the RustFS data directory on whatever schedule matches the risk you’re willing to accept for lost uploads. Attachments are immutable once written, so incremental backups are efficient.

A full restore is a database pg_restore followed by copying the attachment directory back into place. The server and clients do not keep authoritative state anywhere else.

Troubleshooting

The server refuses to start

Check that Postgres is reachable with the DATABASE_URL you provided:

psql $DATABASE_URL -c 'select 1'

If that fails, the server will fail the same way. Fix the connection first.

Migrations fail with “relation already exists”

You’re running migrations against a database that already has tables. If that’s intentional (e.g., you restored from a backup), skip the migration step. If the database is supposed to be fresh, drop it and recreate it before trying again.

Web client says “network error”

The web client proxies /api to the server on port 8000. If the server isn’t running, or is running on a different port, the proxy request fails. Check docker compose ps and the server logs.

Android client can’t reach the server

The emulator uses 10.0.2.2 to reach the host machine’s localhost. A physical device needs the host’s LAN IP address. In both cases, make sure no firewall is blocking the server’s port.

Next steps

Altair is running. Good.