Smart Bookmark Manager with AI-powered organization, tagging and full-text search.
- AI Tagging & Categorization — Automatic tags, folder suggestions and summaries via OpenAI-compatible API (Moonshot/Kimi)
- Unlimited nested folders — Organize bookmarks in arbitrarily deep folder structures
- Full-text search (FTS5) — Blazing-fast search across titles, descriptions and summaries
- Link previews — Automatic fetching of OG images, favicons and metadata
- Telegram Bot — Save links as bookmarks directly from chat
- Userscript — Tampermonkey/Violentmonkey userscript for quick saving via keyboard shortcut
- Import/Export — Netscape HTML, JSON and PDF export
- Shared folders — Share folders with other users
- Dark/Light Mode — Automatic or manual toggle
- User management — First user = admin, controllable registration
- API keys — Programmatic access via API keys
- Dead link check — Detection of broken links
- Multi-language — English and German UI
| Component | Technology |
|---|---|
| Runtime | Bun |
| Backend | Hono |
| Database | SQLite (bun:sqlite) + Drizzle ORM |
| Auth | Better Auth |
| Frontend | Vue 3 + Tailwind CSS 4 |
| State | Pinia |
| AI | OpenAI SDK → Moonshot/Kimi |
| Telegram | grammY |
| PDFKit | |
| Validation | Zod |
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
command: sh -c 'printf "{\n\tdefault_sni %s\n}\n\n%s {\n\ttls internal\n\treverse_proxy keepomat:3000\n}\n" "$$CADDY_HOST" "$$CADDY_HOST" > /etc/caddy/Caddyfile && caddy run --config /etc/caddy/Caddyfile'
ports:
- "443:443"
volumes:
- caddy_data:/data
environment:
- CADDY_HOST=localhost # For LAN access: set to your LAN IP
depends_on:
- keepomat
keepomat:
image: ghcr.io/langfeld/keepomat:latest
container_name: keepomat
restart: unless-stopped
expose:
- "3000"
volumes:
- ./data:/app/data
environment:
- BETTER_AUTH_SECRET=CHANGE_ME # openssl rand -base64 48
- BETTER_AUTH_URL=https://localhost
# - TRUSTED_ORIGINS=https://bookmarks.example.com # optional: Pangolin domain
- MOONSHOT_API_KEY= # optional: AI features
- AI_MODEL=kimi-k2-turbo-preview
- TELEGRAM_BOT_TOKEN= # optional: Telegram bot
- PUID=1000
- PGID=1000
volumes:
caddy_data:No additional config files needed – Caddy runs entirely via command:. It automatically generates a self-signed certificate. Accept it once in the browser, or install Caddy's root CA on your devices (see LAN Access).
docker compose up -dThe app will be available at https://localhost. The first registered user automatically becomes admin.
Without Caddy: Remove the
caddyservice, replaceexposewithports: ["8080:3000"], and setBETTER_AUTH_URL=http://localhost:8080.
| Variable | Description | Required |
|---|---|---|
BETTER_AUTH_SECRET |
Auth secret (min. 32 chars). Generate: openssl rand -base64 48 |
Yes |
BETTER_AUTH_URL |
Public URL of the app (important for cookies) | Yes |
TRUSTED_ORIGINS |
Additional trusted origins, comma-separated (e.g. Pangolin domain) | No |
MOONSHOT_API_KEY |
API key for Moonshot/Kimi AI | No |
AI_MODEL |
AI model to use | No |
TELEGRAM_BOT_TOKEN |
Bot token from @BotFather | No |
PUID / PGID |
User/Group ID for file permissions (default: 1000) | No |
docker run -d \
--name keepomat \
--restart unless-stopped \
-p 8080:3000 \
-v ./data:/app/data \
-e BETTER_AUTH_SECRET=$(openssl rand -base64 48) \
-e BETTER_AUTH_URL=http://localhost:8080 \
ghcr.io/langfeld/keepomat:latestdocker compose pull
docker compose up -dAll data (SQLite database, screenshots) is stored in the ./data volume. Back up this directory to preserve your bookmarks.
To access Keepomat from other devices in your LAN, set CADDY_HOST to your LAN IP and adjust BETTER_AUTH_URL:
# In docker-compose.yml:
caddy:
environment:
- CADDY_HOST=192.168.1.100
keepomat:
environment:
- BETTER_AUTH_URL=https://192.168.1.100
- TRUSTED_ORIGINS=https://bookmarks.example.com # external domain (e.g. Pangolin)No separate config files needed – CADDY_HOST is read at container startup via the command: directive.
If multiple services share the same host (e.g. TrueNAS), bind each to a specific IP via host_ip in the ports section to avoid port conflicts:
ports:
- host_ip: 192.168.1.100
published: 443
target: 443
protocol: tcpAvoid browser warnings: Install Caddy's root CA certificate on your devices:
# Copy the root CA from the Caddy container:
docker cp keepomat-caddy:/data/caddy/pki/authorities/local/root.crt ./caddy-root-ca.crtThen install caddy-root-ca.crt as a trusted certificate on your devices (system settings → certificates).
If you already use an external reverse proxy (Nginx, Traefik, Pangolin), you can bypass Caddy and expose the port directly:
# Remove the caddy service and replace expose with:
ports:
- "8080:3000"Set BETTER_AUTH_URL to your public URL (e.g. https://bookmarks.example.com).
# Install dependencies
bun install
# Run database migrations
bun run db:generate
# Start development server (backend + frontend)
bun run dev- Backend:
http://localhost:3000 - Frontend (Vite):
http://localhost:5173
| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 3000 |
DATABASE_URL |
SQLite database path | ./data/keepomat.db |
BETTER_AUTH_SECRET |
Auth secret (min. 32 chars) | — |
BETTER_AUTH_URL |
Base URL | http://localhost:3000 |
TRUSTED_ORIGINS |
Additional trusted origins (comma-separated) | — |
MOONSHOT_API_KEY |
Moonshot/Kimi API key | — |
AI_MODEL |
AI model | kimi-k2-turbo-preview |
TELEGRAM_BOT_TOKEN |
Telegram bot token | — |
PUID |
User ID (Docker) | 1000 |
PGID |
Group ID (Docker) | 1000 |
- Install a userscript manager (e.g. Tampermonkey or Violentmonkey)
- Install the userscript via
https://<your-url>/keepomat.user.js - Click the 🔖 button on any webpage or press
Alt+K - Enter server URL and API key on first use
All API endpoints under /api/:
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/auth/sign-up/email |
Register |
POST |
/api/auth/sign-in/email |
Login |
GET |
/api/bookmarks |
List bookmarks |
POST |
/api/bookmarks |
Create bookmark |
GET |
/api/folders/tree |
Folder tree |
GET |
/api/search?q=... |
Full-text search |
GET |
/api/export/html |
HTML export |
GET |
/api/health |
Health check |
Authentication via session cookie or X-API-Key / Authorization: Bearer <key> header.
src/
├── server/ # Hono backend (API)
│ ├── routes/ # API routes
│ ├── services/ # Business logic (AI, metadata, screenshots)
│ ├── middleware/ # Auth guards
│ └── utils/ # Helper functions
├── frontend/ # Vue 3 SPA
│ ├── views/ # Pages
│ ├── components/ # Reusable components
│ ├── stores/ # Pinia stores
│ ├── composables/ # Vue composables
│ ├── i18n/ # Translations (en, de)
│ └── router/ # Vue Router
├── db/ # Drizzle ORM (schema, migrations)
├── bot/ # Telegram bot (grammY)
└── shared/ # Shared types & validators