Self-Hosting

Overview

BriefGate cloud (app.briefgate.dev) is the recommended path for most users — no infrastructure to manage, EU data residency included, and you can be up in 5 minutes.

Self-hosting is available if you need data sovereignty on your own infrastructure, on-premises deployment, compliance requirements that cloud does not satisfy, or custom integrations at the server level. The BriefGate server is MIT licensed.

Requirements

Component Minimum version
Node.js 22+
PostgreSQL 16+
Redis 7+
S3-compatible storage Optional (Cloudflare R2, MinIO, AWS S3) — defaults to local disk
Resend API key Recommended — without it, chase emails are no-ops

Quick start with Docker Compose

bash
git clone https://github.com/briefgate/briefgate
cd briefgate
cp .env.example .env
# Edit .env — minimum required variables:
# DATABASE_URL, REDIS_URL, BASE_URL, PORTAL_URL,
# SECRETS_PUBLIC_KEY, SECRETS_PRIVATE_KEY
docker compose up -d

The compose file starts Postgres, Redis, and the BriefGate server. For production, run Postgres and Redis on separate managed instances and point DATABASE_URL and REDIS_URL at them.

Generating the secrets key pair

The secrets vault requires a libsodium keypair. Generate one before first run:

javascript
import libsodium from 'libsodium-wrappers';
await libsodium.ready;
const { publicKey, privateKey } = libsodium.crypto_box_keypair();
console.log('SECRETS_PUBLIC_KEY=' + Buffer.from(publicKey).toString('base64'));
console.log('SECRETS_PRIVATE_KEY=' + Buffer.from(privateKey).toString('base64'));

Store the private key in your secrets manager (Vault, AWS Secrets Manager, 1Password Secrets Automation, etc.). Never commit either key to git.

The public key encrypts values in the client's browser. The private key decrypts on the server. If you rotate the keypair, existing unrevealed secrets cannot be decrypted — rotate only when you intend to start fresh, and only after ensuring all existing secrets have been revealed.

Environment variables

Variable Required Description
DATABASE_URL Yes PostgreSQL connection string (e.g. postgres://user:pass@host:5432/briefgate)
REDIS_URL Yes Redis connection string (e.g. redis://localhost:6379)
BASE_URL Yes Public API URL (e.g. https://api.yourdomain.com)
PORTAL_URL Yes Public portal URL (e.g. https://p.yourdomain.com)
SECRETS_PUBLIC_KEY Yes libsodium public key, base64-encoded
SECRETS_PRIVATE_KEY Yes libsodium private key, base64-encoded. Keep in secrets manager, not .env file in git.
RESEND_API_KEY Recommended Email delivery via Resend. Without it, chase emails and portal invitation emails are no-ops.
ALLOW_SIGNUP First run Set to true to allow account creation. Set to false after creating your account.
STRIPE_SECRET_KEY Optional Billing integration. Without it, all tier limits are disabled (everyone gets Agency-equivalent access).
R2_ENDPOINT Optional S3-compatible endpoint (e.g. https://<account>.r2.cloudflarestorage.com)
R2_ACCESS_KEY_ID Optional S3 access key
R2_SECRET_ACCESS_KEY Optional S3 secret key
R2_BUCKET Optional Bucket name for file storage
STORAGE_LOCAL_DIR Optional Local disk path for file storage when R2 is not configured (default: ./storage)
SECRETS_TTL_DAYS Optional Default secret token expiry in days (default: 30)
TRUST_PROXY Behind a proxy Number of proxy hops in front of the API — 1 behind a single reverse proxy. Default 0 (use the socket address). See the warning below.
CORS_ORIGIN If the portal is on another origin Comma-separated origins allowed to call the API from a browser. Unset = same-origin only.
COOKIE_SECURE Production true to mark session cookies Secure. Defaults to on when NODE_ENV=production.
CLAMAV_HOST Recommended clamd host for virus scanning uploads. Without it uploads are marked skipped and served unscanned.
CLAMAV_TIMEOUT_MS Optional Scan timeout (default: 30000). A timeout marks the file error, and errored files are never served.
SESSION_COOKIE_NAME / PORTAL_COOKIE_NAME Optional Cookie names for the dashboard and client portal sessions (defaults bg_session / bg_portal). Change if you host something else on the same domain.
LOG_LEVEL Optional debug, info, warn, error (default: info)

A warning about TRUST_PROXY

Set it to the number of proxies actually in front of the API, and never to true on a public listener. true means "believe X-Forwarded-For from anyone", which lets a caller choose their own request.ip: they rotate the header to walk around the per-IP brute-force limit on login, and every audit entry records an address of their choosing.

Leaving it at 0 behind a proxy has the opposite failure: every request appears to come from the proxy, so the per-IP limit becomes one global bucket and one noisy client locks out everyone.

Health checks

Endpoint Type Description
GET /healthz Liveness Returns 200 if the process is running
GET /readyz Readiness Returns 200 only if DB and Redis are reachable

Configure your load balancer or orchestrator to use /readyz for routing decisions. Use /healthz for restart policies (a healthy process that cannot reach its DB should not receive traffic but should not be killed).

First login

After docker compose up:

  1. Set ALLOW_SIGNUP=true in your .env and restart (or set it before the first start)
  2. Create your account:
bash
curl -X POST https://api.yourdomain.com/v1/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"...","name":"Your Name"}'
  1. Set ALLOW_SIGNUP=false and restart to prevent additional registrations

If you need multiple accounts (e.g. for a team), create them all while ALLOW_SIGNUP=true, then disable it.

Connecting the MCP package to your instance

The @briefgate/mcp package works with self-hosted deployments. Set the base URL:

bash
# Via environment variable
BRIEFGATE_BASE_URL=https://api.yourdomain.com briefgate-mcp --api-key bg_live_xxxxx
bash
# Via CLI flag
briefgate-mcp --api-key bg_live_xxxxx --base-url https://api.yourdomain.com

Or in your Claude Code MCP config:

bash
claude mcp add briefgate -- briefgate-mcp \
  --api-key bg_live_xxxxx \
  --base-url https://api.yourdomain.com

The portal URL is served from PORTAL_URL — your clients will receive portal links pointing to your domain.