Skip to content

Installation

The easiest way to run a Relay instance is via the OuchNet Docker image:

The current stable release is 0.10.5. The examples below use the rolling latest image; to pin a deployment, replace latest with 0.10.5.

The examples below use a bind mount by default so Relay's data is visible in the installation directory and can be backed up directly. Relay runs as UID 1000 inside the image. With rootful Docker, prepare the directory for that user when necessary:

mkdir -p data
sudo chown -R 1000:1000 data
docker pull git.thc420.dev/ouch/relay:latest
docker run -d \
  --name relay \
  -p 9000:9000 \
  -v "$PWD/data:/var/opt/relay" \
  --restart unless-stopped \
  git.thc420.dev/ouch/relay:latest

The chown example is appropriate only when the host and container UID mapping is direct. With rootless or user-namespaced Docker, an ownership change inside the container may not repair a host bind mount. Provision data for the container's effective UID (or use an ACL), or use the named-volume option below.

Existing data from ~/.thelounge//var/opt/thelounge (pre-rename installs) is picked up automatically if the new path is empty — no manual migration needed.

Or with Docker Compose:

docker compose up -d

Example docker-compose.yml:

services:
  relay:
    image: ${RELAY_IMAGE:-git.thc420.dev/ouch/relay:latest}
    container_name: relay
    ports:
      - "9000:9000"
    volumes:
      # Default: keep Relay data visible on the host.
      - ./data:/var/opt/relay

      # Alternative: Docker-managed storage. Comment out the bind mount above
      # and uncomment this line. Do not enable both entries at once.
      # - relay-data:/var/opt/relay
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:9000/"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s

volumes:
  relay-data:

Create the bind-mount directory before starting the stack:

mkdir -p data
docker compose config
docker compose up -d

You can verify write access using the same UID as the Relay process:

docker compose run --rm --no-deps --user 1000:1000 --entrypoint sh relay \
  -c 'touch /var/opt/relay/.write-test && rm /var/opt/relay/.write-test'

To use a named volume instead, comment out ./data:/var/opt/relay and uncomment relay-data:/var/opt/relay. Leave the top-level relay-data declaration in place. Docker manages the volume's ownership and its contents are not stored in the project directory. Switching between a bind mount and a named volume does not migrate data automatically, so back up and copy the data explicitly before changing the mount. Avoid docker compose down -v unless you intend to delete the named volume and its data.

For a different or pinned image, set RELAY_IMAGE when starting Compose, for example RELAY_IMAGE=git.thc420.dev/ouch/relay:<tag> docker compose up -d.

PostgreSQL message storage

Relay uses SQLite for message history by default. PostgreSQL is also available as an alternative backend for new deployments and existing installations.

To use PostgreSQL, create a database and configure it as the primary history backend in config.yaml:

messageStorage:
  - postgres
postgres:
  connectionString: postgres://user:password@host/database
  maxOpenConns: 10
  maxIdleConns: 5
  connMaxLifetimeMinutes: 60

Starting a PostgreSQL server or container does not switch Relay to PostgreSQL by itself. Relay must have postgres as its primary messageStorage backend and a valid PostgreSQL connectionString before it will use the database.

You can also select PostgreSQL and configure these values from the Admin settings panel. The connection string is write-only in the web UI and is not sent back to the browser. Changing the backend or connection settings requires a server restart. Add text to messageStorage if you also want plain-text message logs.

Existing SQLite history is not removed automatically. For an existing installation, configure PostgreSQL as the primary backend, restart Relay, and then copy the existing history explicitly:

relay storage import-sqlite

To import one user at a time, pass the username:

relay storage import-sqlite <username>

For Docker deployments, run the command inside the Relay container:

docker exec -it relay relay storage import-sqlite

The import leaves the original SQLite files in place. Back up your Relay data before changing storage backends.

Optional message retention policy

Relay can remove old logged messages with a server-side storage policy. This is configured in config.yaml and is disabled by default:

storagePolicy:
  enabled: true
  maxAgeDays: 30
  deletionPolicy: statusOnly

statusOnly removes old status messages while retaining chat messages; everything removes all messages older than maxAgeDays. After enabling or changing the policy, run the cleanup command for an immediate cleanup:

relay storage clean

The command accepts an optional username to clean only one account. Relay also activates the configured cleaner when the server starts. Review the policy carefully before enabling it because deleted history cannot be recovered from Relay.

Optional Docker Compose PostgreSQL stack

If you want PostgreSQL in the same Compose project, add a database service alongside Relay. The PostgreSQL service only provides the database; it does not switch the Relay backend. Merge the following settings into the config.yaml stored in Relay's selected data mount, or configure the same values from the Admin settings panel, then restart Relay:

messageStorage:
  - postgres
postgres:
  connectionString: postgres://relay:change-this-password@postgres:5432/relay?sslmode=disable

Example Compose services:

services:
  relay:
    image: ${RELAY_IMAGE:-git.thc420.dev/ouch/relay:latest}
    container_name: relay
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "9000:9000"
    volumes:
      # Default: keep Relay data visible on the host.
      - ./data:/var/opt/relay

      # Alternative: comment out the bind mount above and uncomment this line.
      # - relay-data:/var/opt/relay
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:9000/"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: relay
      POSTGRES_USER: relay
      POSTGRES_PASSWORD: change-this-password
    volumes:
      - relay-postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U relay -d relay"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  relay-data:
  relay-postgres:

Replace the placeholder password in both places before deploying. For a new Relay instance, no migration is needed. For an existing SQLite installation, start or restart the stack after PostgreSQL is configured as the primary backend, then run docker exec -it relay relay storage import-sqlite to copy the existing history.

Adding the first admin user

The first time Relay starts with no user accounts, it prints a one-time setup token to the container logs:

docker logs relay

Look for a line like:

No users exist yet. To create the first admin account, open the web UI and enter this one-time setup token: <token>

Open the web UI in your browser — you'll land on a setup page automatically. Enter a username, password, and the token from the logs to create the admin account.

Note

The setup page and token are permanently disabled the instant the account is created, so this only works once per install.

Option 2: Command line

If you'd rather not use the web setup page, create the initial admin account with relay add --admin:

docker exec -it relay relay add --admin <username>
docker compose exec relay relay add --admin <username>

Both commands prompt for a password. The new account will be available immediately — no restart needed. Using this method before ever opening the web UI means the setup page will never appear for that install.

Running from source

Building Relay from source requires Node.js LTS or newer for the client build, plus a recent Go toolchain for the server build.

The following commands build and run Relay from source:

git clone https://git.thc420.dev/ouch/relay.git
cd relay
npm install
npm run build:client
go build -o relay ./cmd/relay
./relay start

Relay uses the same config and data layout as the previous backend, so the Go server is intended to be a drop-in replacement for existing installs.

Note

Running the built server does not require Node.js. The main exception is relay migrate-config, which still needs a node binary on your PATH to read a legacy Node-backend config.js.