OpenGather is a self-hosted, privacy-first distributed social network designed to be easy to install and easy to run for real communities.
ONCE is simple hosting for Docker-based web apps: it installs Docker for you, keeps backups, and updates your container image on a schedule.
On the machine that will run OpenGather:
curl https://get.once.com | shUse either the interactive flow or a one-liner.
-
Interactive (
onceTUI): Runonce, choose to install a custom image, and enterghcr.io/rallu/opengather. When prompted, use the hostname that already points at this machine (DNS should resolve to it before you start the TUI). -
Localhost: deploy to localhost with:
once deploy ghcr.io/rallu/opengatherAfter a localhost deploy, the app is usually served at opengather.localhost. If you configured a public hostname in the TUI, use that URL instead.
You can set APP_BASE_URL to that origin in the container environment (recommended for reverse proxies and fixed hostnames). If you omit it, complete first-run setup in the browser at the URL you intend to use; the app stores that origin and uses it for auth and links. See the ONCE-Compatible Container subsection under Custom Installation.
OpenGather is built for communities that want their own space without handing ownership of their conversations, moderation, and member data to a centralized platform.
The project is aimed at:
- self-hosted community networks
- privacy-first deployments
- local ownership of data and moderation rules
- simple operations for small groups and independent operators
The longer-term network model is distributed rather than single-platform. Individual OpenGather deployments can remain autonomous while still participating in a broader ecosystem over time.
OpenGather Hub exists as optional ecosystem infrastructure for identity and discovery, but it is not required for a public open-source deployment of this repo.
This repository includes an ONCE-compatible image that:
- serves HTTP on port
80 - exposes a health endpoint at
GET /up - stores mutable data under
/storage - boots an internal Postgres instance automatically when
DATABASE_URLis not set
Build and run it locally (set APP_BASE_URL to the URL you open in the browser; with -p 8080:80 that is http://localhost:8080):
docker build -t opengather-once .
docker run --rm -p 8080:80 \
-e APP_BASE_URL=http://localhost:8080 \
-v opengather-storage:/storage \
opengather-onceAPP_BASE_URL — Optional but recommended. When set, it overrides the stored setup URL. Canonical origin: scheme, host, and port if it is not the default for that scheme (no trailing slash). Use your real public URL in production (for example https://gather.example.com). For ONCE installs, set it to the hostname you configured (for example https://gather.example.com or http://opengather.localhost) if it should differ from what you confirm during setup. Behind a reverse proxy, setting this to the public HTTPS URL clients use avoids relying on proxy headers alone.
If you leave it unset, the origin from first-run setup is written to $STORAGE_ROOT/app-base-url (default ./storage/app-base-url in development, /storage/app-base-url in the ONCE image) and read synchronously at runtime. APP_BASE_URL still overrides that file when set.
Optional environment variables for the ONCE image:
DATABASE_URLif you want to use an external Postgres instanceBETTER_AUTH_SECRETorSECRET_KEY_BASEfor auth signingVAPID_PUBLIC_KEYandVAPID_PRIVATE_KEYif you want to provide your own web-push keys instead of letting ONCE generate and persist them under/storage/vapid.envVAPID_SUBJECTto override the default web-push subject (APP_BASE_URLwhen set, otherwisemailto:admin@localhost)HUB_BASE_URLonly if you want to override the default production Hub athttps://opengather.netINTERNAL_POSTGRES_DBINTERNAL_POSTGRES_USERINTERNAL_POSTGRES_PASSWORD
If you need to install local runtime dependencies (Docker + PostgreSQL) on an Ubuntu machine, run:
./scripts/install-runtime-deps.shCreate a local test environment (starts PostgreSQL if needed, creates the opengather role/database, and writes .env.local):
./scripts/create-local-test-environment.shThen you can run unit tests against that environment:
npm run test:unitUse a Prisma 7-compatible Node version:
nvm use-
Copy the local env file.
cp .env.example .env
-
Set at least:
DATABASE_URLBETTER_AUTH_SECRET
APP_BASE_URLis optional if you will finish first-run setup in the browser at your real URL (the setup flow saves it). For local development on the default Vite port, setAPP_BASE_URL=http://localhost:5173so behavior matches the address bar before setup.HUB_BASE_URLis optional. In production, OpenGather defaults it tohttps://opengather.net. Set it explicitly only when you want to point an instance at a different Hub or disable that production default during deployment.Browser push notifications also need VAPID keys. ONCE generates them automatically on first boot. For manual/self-hosted installs, generate them once and add them to
.env:npx web-push generate-vapid-keys
Then copy the generated values into:
VAPID_PUBLIC_KEY=... VAPID_PRIVATE_KEY=...
VAPID_SUBJECTis optional. If you omit it, OpenGather usesAPP_BASE_URLwhen available, otherwisemailto:admin@localhost. -
Start Postgres. From the workspace root, the default local database is:
docker compose up -d opengather-db
Default Compose DB URL:
postgres://opengather:opengather@127.0.0.1:5433/opengather -
Start the app:
npm run dev
-
Open http://localhost:5173.
You can populate a local OpenGather instance with predictable demo data using:
npm run seed:test-environmentThis script assumes setup is already complete at /setup, then creates/ensures:
- 5 predefined users
- 10 root posts
- per-post reply threads ranging from 0 to 20 replies
All seeded accounts use password OpenGather123!:
alex@opengather.testjordan@opengather.testsam@opengather.testtaylor@opengather.testmorgan@opengather.test
The script is idempotent for its own records, so you can rerun it to refresh seeded posts/replies without affecting unrelated data.
Chrome DevTools automatic workspaces are available in local development at /.well-known/appspecific/com.chrome.devtools.json, so DevTools can offer a workspace connection when you open the app on localhost.
Core commands:
npm run lint
npm run test:unit
npm run test:e2e
npm run buildThe default Playwright test database URL targets the Docker Compose database on 127.0.0.1:5433.
Test runtime rule:
- Keep the app test port at
5173. - If Playwright startup conflicts with
5173, assume an existing local dev server is already running and reuse it.
Useful project areas:
app/server/*.server.tsfor backend logicapp/routes/*for route loaders, actions, and UIscripts/once-entrypoint.shfor ONCE container boot logicOBSERVABILITY.mdfor metrics and monitoring guidanceBACKUP_RECOVERY.mdfor backup and restore operations
Structured logging, metrics, error monitoring, and backup tooling are already included in the app for self-hosted operations.
OpenGather now supports browser push notifications for in-app notifications such as mentions, replies, and approval requests.
Push delivery has two requirements:
- the user enables the
Pushnotification channel in their profile - the user registers at least one browser/device subscription from the profile page
For ONCE installs, VAPID keys are generated automatically on first boot and persisted at /storage/vapid.env unless you provide your own. For manual installs, generate them yourself with npx web-push generate-vapid-keys and place them in .env.
The service worker is built with Google Workbox as part of npm run build.