Self-hosting Harkly
One machine, Docker Compose, about ten minutes. You get the API, the admin dashboard, the customer portal and a worker, with Postgres and Redis behind them. Harkly is AGPL-3.0; SSO/SAML for admins and audit-log export need a license key, everything else is yours as is.
What you need
- A Linux box with Docker 24+ and Compose v2, 2 CPUs and 4 GB of RAM
- Three hostnames pointing at it, e.g.
api.example.com,app.example.comandfeedback.example.com, with TLS from your own reverse proxy (Caddy, nginx, Traefik) — the containers speak plain HTTP - A Resend API key if you want email to leave the box
Install
git clone https://github.com/Rad-Soft/harkly.git && cd harkly
cp .env.example .envFill in .env:
| setting | value |
|---|---|
HARKLY_API_URL | https://api.example.com — what browsers call |
HARKLY_DASHBOARD_URL | https://app.example.com |
HARKLY_PORTAL_URL | https://feedback.example.com |
COOKIE_DOMAIN | .example.com — the sign-in cookie has to cross from api. to app. |
PORTAL_DEV_SLUG | the slug of the workspace the portal serves (you pick it at sign-up) |
BETTER_AUTH_SECRET, TOKEN_ENCRYPTION_KEY | openssl rand -hex 32 each |
POSTGRES_PASSWORD | anything but the default |
RESEND_API_KEY, EMAIL_FROM | so sign-in codes reach inboxes |
Then:
docker compose pull # or `--build` to build from this checkout
docker compose up -d
docker compose run --rm core bun run doctordoctor prints one line per thing it checked and exits non-zero while
something still needs fixing. Core applies database migrations every time it
starts, so the first up creates the schema.
Point your proxy at the three ports (3000 API, 3100 dashboard, 3200
portal), open https://app.example.com, sign in with your email — the code
is in docker compose logs core until Resend is configured — and create the
workspace with the slug you put in PORTAL_DEV_SLUG.
Reverse proxy example (Caddy)
api.example.com { reverse_proxy localhost:3000 }
app.example.com { reverse_proxy localhost:3100 }
feedback.example.com { reverse_proxy localhost:3200 }
Keep X-Forwarded-For on: the gateway's rate limits and vote integrity go by
the real client IP.
Upgrading
Images are tagged by release (2026.9.16 style) and latest. Pin one in
.env with HARKLY_VERSION=2026.9.16, then:
docker compose pull && docker compose up -dMigrations run on core's start and are additive; the release notes at https://harkly.app/changelog call out anything that isn't.
Backup and restore
Everything lives in Postgres; Redis holds only rate-limit counters and can be lost. Back up nightly:
docker compose exec -T db pg_dump -U harkly -Fc harkly > harkly-$(date +%F).dumpRestore into a fresh install (stop the app services first so nothing writes):
docker compose stop core worker gateway dashboard portal
docker compose exec -T db pg_restore -U harkly -d harkly --clean --if-exists < harkly-2026-09-14.dump
docker compose start core worker gateway dashboard portalThe Postgres volume is harkly-pgdata; snapshotting it (with the container
stopped) is an equally good backup.
License keys
Set HARKLY_LICENSE_KEY in .env and restart core. doctor shows who the
key is for, which features it carries and when it expires. Keys are signed
statements checked offline; nothing phones home.
Telemetry
Off unless HARKLY_TELEMETRY=1. When on, the worker sends one request a day
to api.harkly.app/telemetry with an anonymous instance id (a hash of your
auth secret), the version and row counts per table. No names, emails or
content ever leave your install.
Features that need more than the box
| feature | needs |
|---|---|
| Email support inbox | EMAIL_INBOUND_DOMAIN with MX at Resend, RESEND_WEBHOOK_SECRET, Resend's inbound webhook at https://api.example.com/webhooks/resend |
| Google sign-in | a Google OAuth client, GOOGLE_CLIENT_ID/SECRET, GOOGLE_SIGN_IN=1 |
| AI answers and the agent | ANTHROPIC_API_KEY |
| Slack inbox and DMs | a Slack app, SLACK_CLIENT_ID/SECRET/SIGNING_SECRET, events at https://api.example.com/webhooks/slack |
| Linear sync | a Linear OAuth app, LINEAR_CLIENT_ID/SECRET |
| Custom portal domains, several workspaces per install | not yet on self-host; use PORTAL_DEV_SLUG for one workspace |
Running the dev stack instead
docker compose up db redis plus the bun run --cwd apps/* dev commands in
the README run everything from source with hot reload.