Skip to content

Getting Started

This guide covers running your own Colibri AppView and pointing a client at it. It does not cover deploying the colibri.social website.

An AppView deployment is three services, plus a PDS:

  • The AppView itself (Rust/Rocket), which serves the XRPC API on port 8000.
  • Tap, an ATproto firehose consumer that handles backfill and streams live events to the AppView.
  • A single PostgreSQL database that both Tap and the AppView share.
  • A PDS, which hosts the repository of every community created on your instance. Either one you already run, or the reference one in docker-compose.pds.yml.

In production, docker-compose.yml pulls the published release image and runs the first three together. For local development you run Postgres and Tap from the docker-compose.dev.yml compose file and the AppView with cargo run. See the AppView architecture page for how these fit together and for RAM/CPU sizing guidance, and Development if you’re working on Colibri rather than deploying it.

  • A PDS you control, plus its admin password. This is required: communities are AT Protocol accounts, and the AppView creates and hosts them on this PDS. Community creation, deletion and migration all fail without it. See The PDS below.
  • A domain with TLS, e.g. appview.example.com. This is not optional: the AppView identifies itself with a did:web derived from a hostname, it serves its DID document at /.well-known/did.json, and user PDSs proxy authenticated requests to it over HTTPS. A bare IP will not work.
  • Docker and Docker Compose.
  • Rust 1.96+, if you build locally with cargo run
  • A Linux server meeting the recommended specs. Linux is the only supported and tested platform.
  1. Clone the repository and create your config from the template:

    Terminal window
    git clone https://github.com/colibri-social/appview.git
    cd appview
    cp .env.example .env
  2. Fill in the .env file, then start the dependencies (Postgres and Tap) with the dev compose file:

    Terminal window
    docker compose -f docker-compose.dev.yml up
  3. Run the AppView. Database migrations run automatically on boot:

    Terminal window
    cargo run

    It listens on http://127.0.0.1:8000; API routes live under /xrpc/.

The production docker-compose.yml pulls the published release image (ghcr.io/colibri-social/appview:latest, built from the repository’s Dockerfile) and runs the AppView alongside Postgres and Tap, reading the same .env.

  1. Fill in .env (see Configuration), then bring the whole stack up:

    Terminal window
    docker compose up -d
    # ...or, to run the reference PDS in the same stack (this file is
    # docker-compose.yml plus a `pds` service, so use it instead of that one):
    docker compose -f docker-compose.pds.yml up -d
  2. Put the AppView behind a TLS-terminating reverse proxy (Caddy, nginx, Traefik, etc.) on your domain, forwarding to port 8000.

  3. Confirm your DID document resolves at your domain and lists the #colibri_appview, #colibri_notif, and #colibri_hum service endpoints:

    Terminal window
    curl https://appview.example.com/.well-known/did.json
  4. Confirm the AppView identifies itself correctly:

    Terminal window
    curl https://appview.example.com/xrpc/social.colibri.server.describeServer

    You should get back JSON with "software": "colibri-appview".

Communities are not rows in the AppView’s database. Each one is an AT Protocol account with its own repository, and the PDS at PDS_LOC is what hosts them. social.colibri.community.create mints an invite code and then an account there using PDS_ADMIN_PASS, stores the resulting credentials encrypted, and writes the community’s records to the new repo. Deletion and migration use the same admin access.

PDS_LOC is checked at boot but only used by those endpoints, so a misconfigured PDS doesn’t stop the AppView from starting. It instead fails the first time somebody tries to create a community.

If you don’t already run a PDS, the AppView repository ships docker-compose.pds.yml, docker-compose.yml plus a pds service running the official Bluesky PDS preconfigured for this: admin password shared with the AppView, handle domain derived from APPVIEW_HANDLE_DOMAIN, invite-gated account creation, blobs on a named volume. Run it instead of the plain compose file. It needs PDS_HOSTNAME, PDS_JWT_SECRET and PDS_PLC_ROTATION_KEY in your .env (the file itself documents how to generate the keys), and two things you have to arrange yourself:

  • TLS on PDS_HOSTNAME. The PDS publishes https://${PDS_HOSTNAME} as the service endpoint in every community DID document it registers with the PLC directory, so that’s the URL everyone else will resolve. The compose file publishes the container on 127.0.0.1:3000 by default, for a reverse proxy on the same host, set PDS_BIND_ADDR=0.0.0.0 or attach the service to your proxy’s network if the proxy lives elsewhere.
  • Wildcard DNS for community handles. Handles like c-3lk2....${APPVIEW_HANDLE_DOMAIN} are answered by the PDS, so *.${APPVIEW_HANDLE_DOMAIN} must resolve to it under a certificate that covers it. The simplest correct setup is APPVIEW_HANDLE_DOMAIN equal to PDS_HOSTNAME.

Back up PDS_PLC_ROTATION_KEY. It’s the key that can update the DID documents of every community hosted on that PDS, and losing it is not recoverable.

For running one on a laptop, where a separate docker-compose.pds.dev.yml pairs a localhost PDS with its own private PLC directory, see Running a PDS.

Configuration is read from environment variables (via a .env file in the repo root). The ones you’re most likely to touch:

Variable Required Purpose
APPVIEW_DID Yes The did:web this instance identifies as, e.g. did:web:appview.example.com. Defaults to did:web:api.colibri.social, so a self-hosted instance must set it.
DATABASE_URL Yes Connection string for the shared Postgres database.
K256_PRIVATE_KEY Yes secp256k1 private key used to sign ATproto operations, service-auth tokens, and the DID document. Hex-encoded 32-byte key (64 hex chars, no 0x). Generate one with openssl rand -hex 32
PDS_LOC Yes URL of the PDS where the AppView creates and hosts community repositories. See The PDS.
PDS_ADMIN_PASS Yes Admin password for that PDS, so the AppView can create community accounts.
APPVIEW_HANDLE_DOMAIN Yes Domain communities get their handles from (a community example becomes example.${APPVIEW_HANDLE_DOMAIN}). *.${APPVIEW_HANDLE_DOMAIN} must resolve to the PDS.
PDS_HOSTNAME / PDS_JWT_SECRET / PDS_PLC_ROTATION_KEY Only for the reference PDS Read by the PDS compose files, not by the AppView. Ignored if you point PDS_LOC at a PDS you already operate.
CREDENTIAL_ENCRYPTION_KEY Yes Base64-encoded 32-byte key used to encrypt stored community credentials at rest. Generate one with openssl rand -base64 32
TAP_HOSTNAME / TAP_ADMIN_PASSWORD Yes Where the AppView reaches Tap, and Tap’s admin password.
VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT No Web Push keypair for background notifications. Unset disables background push. Generate a keypair with npx web-push generate-vapid-keys
KLIPY_API_KEY No Enables the GIF picker endpoints. Unset disables them.
HUMMING_ENABLED No Cross-instance presence relaying. On by default, set to false to opt out entirely.
SFU_ANNOUNCED_IP No* Public IP announced in the voice SFU’s ICE candidates. Required behind NAT (most cloud hosts) or clients can’t reach the media ports. See Voice below.

.env.example documents the remaining options. Prefer it for other variables and explainers, the table above only covers the essentials.

Community voice/video runs on an SFU (mediasoup) embedded in the AppView. A few things to get right when self-hosting:

  • Announced IP & media ports. WebRTC media flows directly to the AppView host, not through the reverse proxy. Set SFU_ANNOUNCED_IP to your server’s public IP, and open the port range SFU_RTC_MIN_PORT - SFU_RTC_MAX_PORT (default 40000 - 40100) on your firewall for both UDP and TCP (the SFU falls back to ICE-over-TCP when UDP is blocked). The production docker-compose.yml already publishes this range on the host from those same variables. Without this, users connect but hear no audio.
  • TURN for restrictive networks. A direct path to the media ports fails for clients behind symmetric NAT or strict firewalls. Point them at a TURN server via SFU_ICE_SERVERS, a JSON array of RTCIceServer objects, e.g. [{"urls":["turn:turn.example.com:3478"],"username":"user","credential":"pass"}]. The AppView hands these to every client at call setup. Run your own coturn or use a managed TURN provider. Unset means direct connection only.

The client isn’t tied to any particular AppView. In a running client, open the user settings, then Preferences and set the AppView URL to your instance (https://appview.example.com). The client probes describeServer, accepts the host only if it reports software: "colibri-appview", and then re-authenticates so your session is scoped to the new AppView.

Authentication scopes are pinned to the AppView’s did:web and cannot use a wildcard, so a client will only authorize AppViews it has been configured to trust. That means you generally can’t point an arbitrary hosted client at a brand-new self-hosted AppView and log in. Building and hosting that client UI is out of scope for this guide, the shared UI is published as the @colibri-social/client library (see the client architecture page).

If your community’s members are spread across more than one AppView, off-protocol signals (online status, typing, voice presence) are relayed between instances by Humming, which is on by default. To run a fully isolated instance that never talks to other AppViews, start with HUMMING_ENABLED=false. See the Humming overview for more information.