Skip to content

Self-Hosting ReviewInbox

ReviewInbox self-hosting uses Docker Compose with separate web, api, worker, and postgres services. Run it behind your own HTTPS reverse proxy on a VPS.

ReviewInbox is in early alpha. Pin a semver release and read release notes before upgrading.

  • A Linux VPS with Docker and Docker Compose.
  • A domain name pointing to the VPS.
  • An HTTPS reverse proxy such as Traefik, Caddy, or Nginx.
  • A pinned ReviewInbox release version, for example 0.1.0.

Create an application directory:

Terminal window
sudo mkdir -p /opt/reviewinbox
sudo chown "$USER":"$USER" /opt/reviewinbox
cd /opt/reviewinbox

Download the Compose and environment files:

Terminal window
curl -fsSLO https://raw.githubusercontent.com/bibliocards/reviewinbox/main/docker-compose.self-hosted.yml
curl -fsSLo .env.self-hosted https://raw.githubusercontent.com/bibliocards/reviewinbox/main/.env.self-hosted.example

Generate secrets:

Terminal window
openssl rand -base64 32 # BETTER_AUTH_SECRET
openssl rand -base64 32 # APP_ENCRYPTION_KEY

Edit .env.self-hosted:

Terminal window
nano .env.self-hosted
chmod 600 .env.self-hosted

Set these required values:

  • REVIEWINBOX_VERSION: the exact semver release to run.
  • APP_PUBLIC_URL: your public HTTPS origin, for example https://reviewinbox.example.com.
  • BETTER_AUTH_URL: the same public HTTPS origin when using one domain.
  • BETTER_AUTH_TRUSTED_ORIGINS: the same public HTTPS origin.
  • BETTER_AUTH_SECRET: one generated base64 value.
  • APP_ENCRYPTION_KEY: one generated base64 value. This encrypts Store Credentials.
  • POSTGRES_PASSWORD: a strong database password.

Do not commit .env.self-hosted, attach it to support tickets, or include it in broad log bundles. It contains credentials for auth, Store Credential encryption, Postgres, SMTP, AI, and object storage.

Start the stack:

Terminal window
docker compose --env-file .env.self-hosted -f docker-compose.self-hosted.yml up -d

Check service status:

Terminal window
docker compose --env-file .env.self-hosted -f docker-compose.self-hosted.yml ps
docker compose --env-file .env.self-hosted -f docker-compose.self-hosted.yml logs -f api

AI drafting is optional. Without it, ReviewInbox still imports Reviews and supports manual Reply Draft editing, but it will not generate autogenerated Reply Drafts.

Enable AI-generated Reply Drafts with an OpenAI-compatible provider:

REPLY_DRAFT_WORKER_ENABLED=true
AI_PROVIDER=openai-compatible
AI_MODEL=gpt-4.1-mini
AI_API_KEY=your-provider-key
AI_BASE_URL=

Set REPLY_DRAFT_WORKER_ENABLED=true on the API service so UI actions can enqueue Reply Draft jobs. For drafting alone, set the AI_* values on the worker service. When enabling Review Analysis, configure the same optional TYPESAFE_API_KEY and AI_* values on the API and worker so the API can report classification and discovery availability; the supplied Compose file maps these values to both services. Set AI_BASE_URL only when your provider requires a custom OpenAI-compatible endpoint.

Review analysis is independent from Reply Draft quotas. Jev classifies imported Reviews and the complete initial backfill; Luna proposes missing English topics in bounded daily batches or after a manual request. Discovery is metered separately per Organization, and its commercial limits are intentionally not published until usage has been measured.

Set the optional installation-wide TypeSafe key for the API and worker:

TYPESAFE_API_KEY=your-typesafe-key

Without TYPESAFE_API_KEY, stored analysis remains readable, but new classification and topic-discovery jobs stay unavailable. Review sync and Reply Draft generation continue. The key is operator configuration and is never entered by an Organization owner. Luna reuses the configured AI_PROVIDER, AI_MODEL, AI_API_KEY, and optional AI_BASE_URL values passed to the API and worker.

Analysis processing is durable and resumable. It covers all imported history, uses bounded batches, and records pending, processing, completed, failed, or skipped states. Restarting the worker resumes incomplete work and does not repeatedly submit Reviews already marked completed.

Use one public domain:

  • Route /api/* to 127.0.0.1:3000.
  • Route all other requests to 127.0.0.1:8080.

This keeps Better Auth cookies and browser requests simple. The web container calls the API through relative /api paths by default.

reviewinbox.example.com {
reverse_proxy /api/* 127.0.0.1:3000
reverse_proxy 127.0.0.1:8080
}

Caddy manages HTTPS automatically when the domain points to the VPS.

server {
listen 80;
server_name reviewinbox.example.com;
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

Use Certbot or your preferred ACME client to enable HTTPS.

If Traefik runs on the same Docker host, attach ReviewInbox services to your Traefik network and add labels with a compose override:

services:
api:
networks:
- default
- traefik
labels:
traefik.enable: "true"
traefik.http.routers.reviewinbox-api.rule: Host(`reviewinbox.example.com`) && PathPrefix(`/api`)
traefik.http.routers.reviewinbox-api.entrypoints: websecure
traefik.http.routers.reviewinbox-api.tls.certresolver: letsencrypt
traefik.http.services.reviewinbox-api.loadbalancer.server.port: "3000"
web:
networks:
- default
- traefik
labels:
traefik.enable: "true"
traefik.http.routers.reviewinbox-web.rule: Host(`reviewinbox.example.com`)
traefik.http.routers.reviewinbox-web.entrypoints: websecure
traefik.http.routers.reviewinbox-web.tls.certresolver: letsencrypt
traefik.http.services.reviewinbox-web.loadbalancer.server.port: "80"
networks:
traefik:
external: true

Update the version pin in .env.self-hosted:

REVIEWINBOX_VERSION=0.1.1

Pull and restart:

Terminal window
docker compose --env-file .env.self-hosted -f docker-compose.self-hosted.yml pull
docker compose --env-file .env.self-hosted -f docker-compose.self-hosted.yml up -d

The API service runs pending database migrations on startup in the self-hosted compose file. Keep this to one API instance unless migrations are moved to a dedicated deployment job.

Back up Postgres and uploaded files regularly.

Create a database dump:

Terminal window
docker compose --env-file .env.self-hosted -f docker-compose.self-hosted.yml exec -T postgres pg_dump -U reviewinbox reviewinbox > reviewinbox.sql

List named volumes:

Terminal window
docker volume ls | grep reviewinbox

Back up the postgres-data and uploads volumes with your VPS backup tooling.

View logs:

Terminal window
docker compose --env-file .env.self-hosted -f docker-compose.self-hosted.yml logs -f api worker web

Restart one service:

Terminal window
docker compose --env-file .env.self-hosted -f docker-compose.self-hosted.yml restart worker

Stop the stack:

Terminal window
docker compose --env-file .env.self-hosted -f docker-compose.self-hosted.yml down