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).

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