Self-hosting
Run MarkDB yourself with Docker Compose, and the environment matrix to configure it.
MarkDB is open source. One Go binary runs every role (via a mode flag), backed by three services: PostgreSQL (pgvector), Redis, and Meilisearch.
Run it locally
git clone https://github.com/markdb-cloud/markdb
cd markdb
cp .env.example .env
docker compose upThe root docker-compose.yml brings up:
| Service | Port | Image |
|---|---|---|
markdb (serve) | 8080 | built from Dockerfile |
markdb-proxy | 8090 | same image, -mode proxy |
markdb-worker | — | same image, -mode worker |
postgres | 5437→5432 | pgvector/pgvector:pg17 |
redis | 6379 | redis:8-alpine |
meilisearch | 7700 | getmeili/meilisearch |
Add the dashboard and a local Caddy with docker compose --profile fullstack up.
Binary modes
The mode is set by MARKDB_MODE (or -mode):
| Mode | Role |
|---|---|
serve | HTTP API + MCP server (the data plane and control plane). |
proxy | The LLM capture proxy. |
worker | Enrichment + indexing background worker. |
stdio-mcp | MCP over stdio (for local/desktop clients). |
migrate | Apply migrations and exit. |
Migrations are applied automatically on startup in every mode (a custom,
forward-only, filename-ordered runner over db/migrations); there are no down
migrations.
Backing services
- PostgreSQL + pgvector (
pgvector/pgvector:pg17) -- the system of record; requires thevector,pgcrypto, andcitextextensions (all in that image). Ashm_sizeof at least1gis recommended. - Redis (
redis:8-alpine) -- cache, rate limiting, and chat-activity tracking. If Redis is unavailable,servefalls back to an in-memory cache and rate limiting is disabled. - Meilisearch (cloud pins
v1.43.0) -- keyword search. If it's unavailable, search falls back to Postgres keyword search.
Environment matrix
The key MARKDB_* variables (plus DATABASE_URL and REDIS_URL), with defaults:
Core
| Var | Default |
|---|---|
MARKDB_MODE | serve |
MARKDB_HTTP_ADDR | :8080 |
MARKDB_PROXY_HTTP_ADDR | :8090 |
MARKDB_MCP_PATH | /mcp |
MARKDB_LOG_LEVEL | info |
MARKDB_MIGRATIONS_DIR | db/migrations |
Data stores
| Var | Default |
|---|---|
DATABASE_URL | postgres://markdb:markdb@postgres:5432/markdb?sslmode=disable |
MARKDB_DB_POOL_SIZE | 16 |
REDIS_URL | redis://redis:6379/0 |
MARKDB_REDIS_ENABLED | true |
Search
| Var | Default |
|---|---|
MARKDB_SEARCH_ENABLED | true |
MARKDB_SEARCH_DIMENSIONS | 3072 |
MARKDB_MEILISEARCH_URL | http://meilisearch:7700 |
MARKDB_MEILISEARCH_API_KEY | — |
MARKDB_MEILISEARCH_INDEX | markdb_pages |
Proxy & worker
| Var | Default |
|---|---|
MARKDB_PROXY_USE_NATIVE_SDK | "" (empty = all) |
MARKDB_PROXY_INJECT_CONTEXT | true |
MARKDB_PROXY_CHAT_IDLE_TIMEOUT | 3h |
MARKDB_ENRICHMENT_ENABLED | false (worker compose sets true) |
MARKDB_WORKER_PROXY_URL | — |
MARKDB_WORKER_SERVICE_TOKEN | — |
ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY / XAI_API_KEY | — |
Auth & keys
| Var | Default |
|---|---|
MARKDB_AUTH_MODE | headers (headers, bearer, disabled, jwt, jwt+bearer, service) |
MARKDB_MCP_READ_ONLY | false |
MARKDB_JWT_SECRET | — (≥32 chars for JWT modes) |
MARKDB_GITHUB_CLIENT_ID / _SECRET | — |
MARKDB_GOOGLE_CLIENT_ID / _SECRET | — |
MARKDB_LOCAL_KEK_BASE64 | — (base64 of 32 bytes; see Security) |
MARKDB_RATE_LIMIT_PERSONAL_PER_MINUTE | 60 |
MARKDB_RATE_LIMIT_TEAM_PER_MINUTE | 600 |
Public URLs (used to build links and set cookie/OAuth redirects): MARKDB_PUBLIC_API_URL, MARKDB_PUBLIC_DASHBOARD_URL, MARKDB_PUBLIC_PROXY_URL, MARKDB_PUBLIC_MCP_URL, MARKDB_PUBLIC_BASE_URL.
Embedding dimensions
.env.example ships MARKDB_SEARCH_DIMENSIONS=1536, but the default embedder
path (gemini-embedding-001) and both compose files use 3072. Whatever you
pick, index-time and query-time dimensions must match, so set it once and rebuild
the index if you change it.
Single-VM production
deploy/cloud/ contains a production reference: a docker-compose.yml with
Caddy terminating TLS (auto Let's Encrypt) for the api, mcp, proxy, and
dashboard hosts, plus Terraform (deploy/cloud/terraform/) that provisions a GCP
VM, static IP, Artifact Registry, a backups bucket, a KMS key, and Secret Manager
entries. DNS A records are pointed at the VM manually. See
deploy/cloud/terraform/README.md in the repo.