Markcat Docs Open app →

Self-Hosting Guide

User Types

Type Description Database LLM
A Everything local SQLite Ollama (local) or cloud API
B Remote service, local Ollama PostgreSQL Ollama
C Remote service, cloud LLM PostgreSQL OpenRouter / OpenAI / Anthropic
D Fully hosted (markcat.app) PostgreSQL OpenRouter (managed)

This guide covers types A, B, and C.

User A — Local (Default)

No configuration needed. SQLite database + SQLite blob storage.

pip install -r service/requirements.txt
python -m service.main
# → http://127.0.0.1:8080

Moving blobs out of SQLite

If your database grows large, move blobs (favicons, OG images) to the filesystem:

export BLOB_BACKEND=local
python -m service.scripts.migrate_blobs_to_storage --execute
python -m service.main

User B/C — Self-Hosted with PostgreSQL

# 1. Configure environment
export DB_BACKEND=postgres
export DATABASE_URL=postgres://user:pass@server:5432/markcat
export BLOB_BACKEND=local
export BLOB_DIR=/data/markcat/blobs

# 2. Start service (creates schema automatically)
python -m service.main

# 3. (Optional) Migrate from existing SQLite
python -m service.scripts.export_to_postgres --execute
python -m service.scripts.migrate_blobs_to_storage --execute

Docker Deployment

For a production setup with Caddy reverse proxy and auto-TLS:

# See deploy/ directory for Docker Compose and Caddyfile
# Key components: FastAPI (port 8080) + PostgreSQL (5432) + Caddy (443)

The repository’s deploy/ directory contains the Docker Compose files and Caddyfile for a full VPS deployment.

Serve over HTTPS for PWA install & Android share

The PWA only installs in a secure context — HTTPS on any hostname, or localhost/127.0.0.1. Installing the PWA is also what registers Markcat in Android’s Share sheet (share-as-bookmark). Consequences for self-hosters:

This adds no requirement beyond what PWA install already needs — if “Add to Home Screen” works, the Android share target works.

Environment Variables

Required (Cloud Mode)

Variable Description
DB_BACKEND postgres
DATABASE_URL PostgreSQL connection string
BLOB_BACKEND local (recommended)
BLOB_DIR Path to blob storage directory

Authentication (Multi-Tenant)

Variable Description
AUTH_MODE jwt for multi-tenant mode
JWT_SECRET Secret key for JWT signing
SETTINGS_ENCRYPTION_KEY Required when AUTH_MODE=jwt. Encrypts BYOK API keys at rest (AES-GCM). Generate with python -c "import os, base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())".
CORS_ORIGINS Allowed origins (comma-separated)

Billing (Optional)

Variable Description
STRIPE_SECRET_KEY Stripe API key
STRIPE_WEBHOOK_SECRET Stripe webhook signing secret
STRIPE_PRICE_BASIC_MONTHLY / _ANNUAL Stripe price IDs for Basic plans
STRIPE_PRICE_PRO_MONTHLY / _ANNUAL Stripe price IDs for Pro plans
STRIPE_PRICE_TOPUP_500 / _2K / _10K Stripe price IDs for top-up packs

The repository’s docs/reference/deployment.md holds the complete environment variable reference.

Database Backends

Backend File Concurrency
SQLite Single file, WAL mode One process only
PostgreSQL Connection pool (2-10) Multiple instances OK

Utility Scripts

Run with python -m service.scripts.<name>:

Script Purpose
export_to_postgres Copy SQLite data to PostgreSQL
migrate_blobs_to_storage Move blobs from SQLite to filesystem
fix_duplicates Deduplicate bookmarks by normalized URL
fix_favicons Re-fetch missing favicons
find_dead_sites Detect dead bookmarks
compact_categories LLM-powered category merging

The repository’s docs/reference/scripts.md lists every script.