Installation¶
Recommended Usage¶
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¶
Option 1: Web setup (recommended)¶
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.