Self-host - Docker Compose
Self-host OpenQuok with Docker Compose — env setup, Supabase, bring-up, and updates for API, web, Redis, and BullMQ workers.
Connect your agent today
Draft from chat, review in your calendar, and publish only what you approve.
Overview
You run OpenQuok on your computer or private network with Docker Compose. The stack lives under infra/self-host/. It starts the API, web UI, Redis, and background workers.
Tip
You can pull pre-built API and worker images instead of building them. You still need this repo for Compose files and infra/self-host/.env. See Docker (pre-built images).
Note
infra/docker-compose.yml is for repo developers (Redis only). Operators use infra/self-host/docker-compose.yml.
Self-host defaults turn off email and billing. You can sign up without inbox verification or a paywall. See email and Stripe.
Prepare your environment file
Copy the example file from the repo root:
cp infra/self-host/.env.example infra/self-host/.env Compose loads infra/self-host/.env into the API and workers. The web service does not load the full file. VITE_* values are set at web image build time.
Supabase (required)
You need a Supabase project before the first start. Apply OpenQuok migrations to that project. See Database & migrations and Supabase.
| Variable | Role |
|---|---|
| PUBLIC_SUPABASE_URL | API / Auth URL |
| PUBLIC_SUPABASE_PUBLISHABLE_KEY | Publishable key |
| SUPABASE_SECRET_KEY | Secret key (server only) |
| VITE_PUBLIC_SUPABASE_URL | Same URL for the web build |
| VITE_PUBLIC_SUPABASE_PUBLISHABLE_KEY | Same publishable key for the web build |
If Supabase runs on your host with supabase start, use http://host.docker.internal:54321 for the API URL (port from supabase status, not Postgres 5432).
Local-friendly defaults
EMAIL_ENABLED=false
STRIPE_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=
DISABLE_REGISTRATION=false
NOT_SECURED=true
FRONTEND_DOMAIN_URL=http://localhost:4007
BACKEND_DOMAIN_URL=http://localhost:3000
VITE_FRONTEND_DOMAIN_URL=http://localhost:4007
VITE_API_BASE_URL= Set SECURITY_SECRET (for example openssl rand -hex 32). Leave VITE_API_BASE_URL empty so the web container proxies /api to the API service.
Use NOT_SECURED=true for plain HTTP on localhost. Set false behind HTTPS. With true, API rate limiting is off by default (see RATE_LIMIT_ENABLED in infra/self-host/.env.example).
Redis and uploads use Compose defaults (REDIS_HOST=redis, STORAGE_PROVIDER=local).
Social channels (optional)
Leave provider keys empty until you connect a channel. Fill only the networks you use. Then recreate API and workers. Setup guides: Social integration.
| Variable | Channel |
|---|---|
| FACEBOOK_APP_ID / FACEBOOK_APP_SECRET | Facebook Page |
| INSTAGRAM_APP_ID / INSTAGRAM_APP_SECRET | |
| THREADS_APP_ID / THREADS_APP_SECRET | Meta Threads |
| YOUTUBE_CLIENT_ID / YOUTUBE_CLIENT_SECRET | YouTube |
| TIKTOK_CLIENT_ID / TIKTOK_CLIENT_SECRET | TikTok |
| LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET | LinkedIn / LinkedIn Page |
| X_API_KEY / X_API_SECRET | X |
Dev.to uses a user API key in the dashboard; Bluesky uses an app password in Add Channel. You do not add operator env vars for either. See Dev.to and Bluesky.
For cutover windows, set MAINTENANCE_MODE and recreate containers. See Maintenance mode.
After you change env
Rebuild the web service when you change VITE_*. For other vars, recreate: docker compose -f infra/self-host/docker-compose.yml up -d --force-recreate.
Install and start
Run all commands from the repository root.
Check requirements
See System requirements for Docker, disk space, and Supabase.
Edit .env
Copy infra/self-host/.env.example to infra/self-host/.env. Fill Supabase keys and SECURITY_SECRET.
Start the stack
Option A — Build everything (default)
docker compose -f infra/self-host/docker-compose.yml up --build The first run can take 20–40 minutes or more. Later starts are faster. Add -d to run in the background.
Option B — Pull API and workers
Set in infra/self-host/.env:
OPENQUOK_IMAGE_REGISTRY=ghcr.io/ratimon
OPENQUOK_IMAGE_TAG=0.1.1 Then:
docker compose -f infra/self-host/docker-compose.yml -f infra/self-host/docker-compose.images.yml pull
docker compose -f infra/self-host/docker-compose.yml -f infra/self-host/docker-compose.images.yml up -d --build Use the same two -f files for down, logs, and updates when you chose Option B.
Open the app
- Web: http://localhost:4007
- API: http://localhost:3000
Change the web port with OPENQUOK_WEB_HOST_PORT if 4007 is busy.
Optional: CLI login profile
For openquok auth:login device flow:
docker compose -f infra/self-host/docker-compose.yml --profile cli up --build Register an OAuth app with redirect http://localhost:3111/device/callback. See Configuration - Agent.
Stop the stack
Option A:
docker compose -f infra/self-host/docker-compose.yml down Option B (both compose files):
docker compose -f infra/self-host/docker-compose.yml -f infra/self-host/docker-compose.images.yml down Add —profile cli if you used the CLI profile.
Start again
Reuse images when you did not change code or VITE_*:
docker compose -f infra/self-host/docker-compose.yml up -d --no-build With Option B, add the second -f infra/self-host/docker-compose.images.yml file. Open http://localhost:4007 when containers are up.
Update
From the repo root:
git pull origin main
docker compose -f infra/self-host/docker-compose.yml up -d --build For Option B, set a new OPENQUOK_IMAGE_TAG, then pull and up -d —build with both compose files.
If release notes mention database changes, run migrations on Supabase. Docker does not do that step. See Database & migrations.
What runs in the stack
| Service | Role |
|---|---|
redis | Cache and job queue |
api | Backend API |
web | Web UI |
worker-* (three) | Background jobs |
postgres / agent-server | Only with —profile cli |
Troubleshooting
Warning
If up --build fails on registry-1.docker.io, run docker logout docker.io and retry. For Option B, use OPENQUOK_IMAGE_REGISTRY=ghcr.io/ratimon when GHCR packages are public.
If orchestrator build fails with already exists, update the repo or use Option B pull.
Security and exposure
This stack targets your machine or a trusted private network. It is not a hardened public SaaS edge.
| Topic | Guidance |
|---|---|
| HTTP | NOT_SECURED=true on localhost only; use HTTPS and false on the public internet |
| Signup | Open by default; email verify is off when EMAIL_ENABLED=false |
| Secrets | Never commit infra/self-host/.env |
| Ports | Web and API bind on the host; use a firewall on shared or cloud hosts |
| Redis | No password; not published to the host |
Danger
Do not expose this stack to the internet without TLS, a firewall, and NOT_SECURED=false. Rotate secrets if they leak.
For Vercel or Railway deploys, see Production deployment.