Skip to content

Self-host - Docker Compose

Self-host OpenQuok with Docker Compose — env setup, Supabase, and bring-up of API, web, Redis, and BullMQ workers.

6 min read

Connect your agent today

Draft from chat, review in your calendar, and publish only what you approve.

Start for $0

Overview

The canonical self-host path builds images from this monorepo and runs API, web, Redis, and BullMQ workers via Compose under infra/self-host/. Use it when you want OpenQuok on your own computer or private network — not the hosted product at openquok.com.

Self-host defaults skip outbound email and Stripe billing so you can sign up and enter the app without a verify inbox or First Billing paywall. Details: email and Stripe.

Environment variables

Copy the example file, then edit infra/self-host/.env. Compose loads it into the API and workers (and agent when using the cli profile) via env_file; the web service does not load the full file. You can also export the same names in the shell before docker compose.

# From repo root
cp infra/self-host/.env.example infra/self-host/.env

Required: Supabase

Fill these before first start (hosted project or local supabase start). Apply OpenQuok migrations to that project — see Database & migrations and Supabase.

VariableRole
PUBLIC_SUPABASE_URLAPI / Auth URL
PUBLIC_SUPABASE_PUBLISHABLE_KEYPublishable key
SUPABASE_SECRET_KEYSecret key (server only)
VITE_PUBLIC_SUPABASE_URLSame URL for the web build
VITE_PUBLIC_SUPABASE_PUBLISHABLE_KEYSame publishable key for the web build

Optional: Social provider apps

Leave these empty until you connect a channel. Fill only the pairs for networks you use, then recreate the API (and workers) so they pick up the new env. Per-provider setup (redirect URIs, app dashboards): Social integration.

VariableChannel
FACEBOOK_APP_ID / FACEBOOK_APP_SECRETFacebook Page
INSTAGRAM_APP_ID / INSTAGRAM_APP_SECRETInstagram
THREADS_APP_ID / THREADS_APP_SECRETMeta Threads
YOUTUBE_CLIENT_ID / YOUTUBE_CLIENT_SECRETYouTube
TIKTOK_CLIENT_ID / TIKTOK_CLIENT_SECRETTikTok
LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRETLinkedIn / LinkedIn Page
X_API_KEY / X_API_SECRETX

Self-host friendly defaults

EMAIL_ENABLED=false
STRIPE_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=
DISABLE_REGISTRATION=false
NOT_SECURED=true
  • EMAIL_ENABLED =false — no outbound mail; signup auto-verified.
  • Empty STRIPE_PUBLISHABLE_KEYbillingEnabled=false; no First Billing paywall.
  • NOT_SECURED =true for plain HTTP on localhost; set false behind an HTTPS reverse proxy. With true, API rate limiting is off by default so the web container’s SSR proxy does not exhaust a shared per-IP limit (see RATE_LIMIT_ENABLED in infra/self-host/.env.example).

Also set SECURITY_SECRET (for example openssl rand -hex 32) and align public URLs:

FRONTEND_DOMAIN_URL=http://localhost:4007
BACKEND_DOMAIN_URL=http://localhost:3000
VITE_FRONTEND_DOMAIN_URL=http://localhost:4007
VITE_API_BASE_URL=

Leave VITE_API_BASE_URL empty for this Compose stack. The web container proxies same-origin /api and /uploads to the api service (OPENQUOK_API_PROXY_TARGET in Compose). Setting http://localhost:3000 breaks server-side rendering inside the web container.

Redis and storage defaults point at Compose service DNS and the uploads volume (REDIS_HOST=redis, STORAGE_PROVIDER=local, UPLOAD_DIRECTORY=/uploads).

For database cutover or maintenance, set MAINTENANCE_MODE in infra/self-host/.env (default off). The web service receives it at runtime via Compose. Recreate API, web, and worker containers after changes. See Maintenance mode.

Install and start

Meet system requirements

Confirm Docker / Compose, disk, and a Supabase project per System requirements.

Configure .env

Copy infra/self-host/.env.example to infra/self-host/.env, fill Supabase keys (and matching VITE_*), and set SECURITY_SECRET.

Bring the stack up

From the repository root:

docker compose -f infra/self-host/docker-compose.yml up --build

Detach with -d when you want background containers. The web service waits until api passes its /health check so SSR does not call the API before it is listening.

Open the UI

  • Web UI: http://localhost:4007
  • API: http://localhost:3000

Override the web host port with OPENQUOK_WEB_HOST_PORT if 4007 is taken; then open that port instead.

Sign up with email/password; with the defaults above you should enter the app without email verification or a billing paywall.

Optional: CLI device-flow profile

Postgres + agent server for openquok auth:login device flow:

docker compose -f infra/self-host/docker-compose.yml --profile cli up --build

Register an OAuth app (Settings → Developers → Apps) with redirect http://localhost:3111/device/callback, and fill the OPENQUOKOAUTH* / CLI_AUTH_SERVER_URL fields documented in the example env. See Configuration - Agent and Admin — OAuth Server.

Stop the stack

# From repo root
docker compose -f infra/self-host/docker-compose.yml down

If you started with —profile cli, include the same profile so Compose tears down those services too:

docker compose -f infra/self-host/docker-compose.yml --profile cli down

Services in the stack

Compose serviceRole
redisCache + BullMQ
apiExpress backend
webSvelteKit Node adapter
worker-integration-refreshBullMQ worker
worker-notification-emailBullMQ worker
worker-scheduled-social-postBullMQ worker
postgres / agent-serverOnly with —profile cli

Images build from the monorepo root (context: ../..). No registry is required for v1.

Security and exposure

This stack is designed for trusted local / private-network operators, not a multi-tenant public SaaS edge. Keep that in mind before publishing ports to the internet.

TopicDefault / behaviorGuidance
AudienceSingle operator machinePrefer localhost; for LAN/VPN only after you understand the defaults below
NOT_SECUREDtrue (HTTP cookies)Set false behind HTTPS; never leave true on a public origin
RegistrationDISABLE_REGISTRATION=falseOpen signup; with EMAIL_ENABLED=false accounts skip email verify
Secrets fileinfra/self-host/.envGitignored; never commit. Compose loads it into API and workers (and agent when using cli)
Web containerNo full env_fileOnly runtime vars + VITE_* baked at build; do not pass SUPABASE_SECRET_KEY into web
Published portsWeb, API (and optional agent / worker health) on the hostBind to localhost or firewall if the host is multi-user / cloud VPS
RedisNo password; not publishedKeep unpublished; do not point at a shared production Redis
CLI PostgresDefault openquok / openquok; profile cli onlyChange credentials if the host is not solely yours

Production notes

  • Put a reverse proxy (TLS) in front of web and API when exposing beyond localhost; set NOT_SECURED to false and update public URL vars to your HTTPS origins.
  • SaaS-style deploys on Vercel / Railway remain documented under Production deployment; this page is the self-host Compose path.
Search documentation
Find a docs page
Discord Support