Karakeep with Docker: Bookmark Everything, Auto-Tagged by a Local LLM via Ollama (Linux, Windows, Mac)
Before you start
- 16 min read
Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#
Karakeep is a self-hosted "bookmark everything" app: you save links, notes and images, it archives the pages, makes them searchable and, with a local model in Ollama, tags them for you.

Logo: the official Karakeep project (github.com/karakeep-app/karakeep)
Think of it as a read-later list that never loses a page. A headless Chrome opens each link and keeps the readable text, so you can still find it when the website is gone. Meilisearch gives you instant search. The AI tags are the fun part: no sorting by hand, and with Ollama nothing leaves your machine.
At a glance#
- What you get: a web app on port 3000, browser extensions, phone apps, full-text search, lists, tags and optional full-page archives.
- Time needed: about 15 minutes, plus the time Ollama needs to download a model.
- Difficulty: easy. One Compose file, one
.envfile. - You need: Docker with the Compose plugin, a browser, and Ollama (optional, for AI tags).
| What | Value |
|---|---|
| Current version | v0.34.1, released 22 Oct 2026 (GitHub releases) |
| Pinned images | ghcr.io/karakeep-app/karakeep:0.34.1, ghcr.io/karakeep-app/karakeep-chrome:release, getmeili/meilisearch:v1.41.0 |
| Port | 3000 (web page and API) |
| Official docs | docs.karakeep.app |
Why this version? v0.34.1 is a security release on top of 0.34.0: it moves the images to a newer Node.js that fixes October 2026 Node security issues. We looked at the issues opened since the release. We found no install failures and no data-loss reports. One login problem exists after the 0.34 auth-library change (see Troubleshooting, "Invalid Origin"), and the maintainer says fixing NEXTAUTH_URL solves it. So this guide uses the latest release. Your rollback tag is 0.34.0. The Chrome image has no version tags (the 0.34.1 tag does not exist), so it follows release, as in the official Compose file.
How it fits together#
You save a link, the web container crawls it with Chrome, indexes it in Meilisearch, stores it in your data folder and asks Ollama for tags.
Ollama is not part of the Compose file. You run it on your host (or another machine) and point Karakeep at it.
Before you start#
You need Docker, about 2 GB of free RAM for Karakeep itself, and enough RAM or GPU for the Ollama model you pick.
| System | What to install |
|---|---|
| Linux | Docker Engine + Compose plugin; Ollama |
| Windows | Docker Desktop (WSL2 backend); Ollama for Windows |
| Mac | Docker Desktop; Ollama for Mac |
Check your tools:
docker --version docker compose version
Checklist:
- [ ] Port 3000 is free
- [ ] You backed up any config you are about to replace
- [ ] Ollama runs and answers on port 11434 (see our Ollama guide if you need to install it)
Tested on 2026-10-26: not run. Commands checked against the official Karakeep docs and the Compose file at tag v0.34.1. The 2 GB figure is our rough estimate, not an official number.
Step 1: Create the folders and the .env file#
Karakeep needs two secrets and the address you will use. Generate them once.
Linux#
mkdir -p ~/docker_volumes/karakeep/{data,meili_data,backups}
cd ~/docker_volumes/karakeep
cat > .env <<EOF
KARAKEEP_VERSION=0.34.1
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=$(openssl rand -base64 36 | tr -dc 'A-Za-z0-9')
MEILI_MASTER_KEY=$(openssl rand -base64 36 | tr -dc 'A-Za-z0-9')
EOF
chmod 600 .env
Windows (PowerShell)#
$d = "$HOME\docker_volumes\karakeep"
"data","meili_data","backups" | ForEach-Object { New-Item -ItemType Directory -Force "$d\$_" | Out-Null }
Set-Location $d
function New-Secret { -join ((48..57 + 65..90 + 97..122) | Get-Random -Count 48 | ForEach-Object { [char]$_ }) }
@"
KARAKEEP_VERSION=0.34.1
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=$(New-Secret)
MEILI_MASTER_KEY=$(New-Secret)
"@ | Set-Content -Encoding ascii .env
Mac (Terminal)#
Same commands as Linux:
mkdir -p ~/docker_volumes/karakeep/{data,meili_data,backups}
cd ~/docker_volumes/karakeep
cat > .env <<EOF
KARAKEEP_VERSION=0.34.1
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=$(openssl rand -base64 36 | tr -dc 'A-Za-z0-9')
MEILI_MASTER_KEY=$(openssl rand -base64 36 | tr -dc 'A-Za-z0-9')
EOF
chmod 600 .env
Change NEXTAUTH_URL to the address you really type in the browser (for example your server's LAN address or your https domain). A wrong value causes login errors.
Check it worked#
cat .env
Expected: four lines, two of them with long random strings.
Step 2: Save the Compose file#
This is the official Compose file with three changes: the data lives in ~/docker_volumes/karakeep, the Karakeep image is pinned through .env, and the Ollama settings are added.
Open the file in an editor first:
Linux#
cd ~/docker_volumes/karakeep nano docker-compose.yml
Windows (PowerShell)#
Set-Location "$HOME\docker_volumes\karakeep" notepad docker-compose.yml
Mac (Terminal)#
Same commands as Linux:
cd ~/docker_volumes/karakeep nano docker-compose.yml
Paste this into docker-compose.yml on every system and save:
services:
web:
image: ghcr.io/karakeep-app/karakeep:${KARAKEEP_VERSION:-release}
restart: unless-stopped
volumes:
- ./data:/data
ports:
- 3000:3000
env_file:
- .env
environment:
MEILI_ADDR: http://meilisearch:7700
BROWSER_WEB_URL: http://chrome:9222
DATA_DIR: /data # DON'T CHANGE THIS
# Ollama on the Docker host, through its OpenAI-compatible endpoint
OPENAI_API_KEY: ollama
OPENAI_BASE_URL: http://host.docker.internal:11434/v1
INFERENCE_TEXT_MODEL: gemma3
INFERENCE_IMAGE_MODEL: llava
extra_hosts:
- "host.docker.internal:host-gateway"
chrome:
image: ghcr.io/karakeep-app/karakeep-chrome:release
restart: unless-stopped
init: true
command:
- --disable-gpu
- --disable-dev-shm-usage
- --hide-scrollbars
- --disable-blink-features=AutomationControlled
- --window-size=1440,900
meilisearch:
image: getmeili/meilisearch:v1.41.0
restart: unless-stopped
env_file:
- .env
environment:
MEILI_NO_ANALYTICS: "true"
volumes:
- ./meili_data:/meili_data
Notes:
- The official file calls the three services
web,chromeandmeilisearch. Keep these names:BROWSER_WEB_URLandMEILI_ADDRdepend on them. extra_hostsmakeshost.docker.internalwork on Linux. Docker Desktop (Windows, Mac) already has that name, and the line does no harm there.- No Ollama yet? Delete the Ollama lines (
OPENAI_API_KEYtoextra_hosts). Karakeep then runs without AI tags. MEILI_MASTER_KEYis read from.envby bothwebandmeilisearch. Do not change it later, or search stops working.
Check it worked#
docker compose config --quiet && echo OK
Expected: OK.
Step 3: Get Ollama ready for Karakeep#
Karakeep runs inside a container, so localhost means the container itself. Ollama must answer on an address the container can reach.
Pull the two models used in the official docs (the first writes tags, the second describes images):
Linux#
ollama pull gemma3 ollama pull llava
Ollama listens on 127.0.0.1 only by default. A container reaches the host through the Docker bridge, so let Ollama listen on all interfaces:
sudo systemctl edit ollama
Add these two lines, save, then restart:
[Service] Environment="OLLAMA_HOST=0.0.0.0:11434"
sudo systemctl restart ollama
Keep port 11434 closed to the internet on your router and firewall. Anyone who can reach it can use your model.
Windows (PowerShell)#
ollama pull gemma3 ollama pull llava
Docker Desktop containers reach Windows apps through host.docker.internal. If the check below fails, set the user variable OLLAMA_HOST to 0.0.0.0:11434 in Windows settings, then quit and start Ollama again.
Mac (Terminal)#
ollama pull gemma3 ollama pull llava
If the check below fails, run launchctl setenv OLLAMA_HOST "0.0.0.0:11434" and restart the Ollama app.
Check it worked#
From inside a container (the same command works on all three systems):
docker compose run --rm --entrypoint "" web wget -qO- http://host.docker.internal:11434/api/tags
Expected: a JSON list that includes gemma3 and llava.
Step 4: Start Karakeep#
One command starts the three containers.
Linux#
cd ~/docker_volumes/karakeep docker compose up -d
Windows (PowerShell)#
Set-Location "$HOME\docker_volumes\karakeep" docker compose up -d
Mac (Terminal)#
Same commands as Linux:
cd ~/docker_volumes/karakeep docker compose up -d
Check it worked#
docker compose ps
Expected: web, chrome and meilisearch are running. Open http://localhost:3000. You should see the sign-in page.
First run: create your account#
The first account you create is the admin. Then close the door for everyone else.
- Open
http://localhost:3000and click Sign Up. - Enter name, email and a strong password.
- Save a first link with the + button, for example a page you like.
- Wait a few seconds. The card gets a preview image, and if Ollama works it also gets tags.
Now stop other people from creating accounts. Add this line to .env:
DISABLE_SIGNUPS=true
Apply it:
docker compose up -d
The sign-up button disappears. Remove the line the day you want to add a family member.
Check it worked#
Your bookmark shows a few tags after a short wait. No tags? Read the logs with docker compose logs web --tail 100 and see Troubleshooting.
Use it every day#
Install the extension and the phone app, import your old bookmarks and switch on the page archive.
| You want | Do this |
|---|---|
| Save from the browser | Install the Chrome extension or the Firefox add-on. Enter your server address and sign in |
| Save from the phone | Install the iOS or Android app, then use the share menu. Your phone must reach the server address |
| Import from Chrome or Firefox | Export bookmarks as HTML in the browser, then in Karakeep go to Settings and click Import Bookmarks from HTML file |
| Import from Pocket or Omnivore | Settings, import. Pocket gives a CSV, Omnivore gives JSON files (see the import page) |
| Keep a full copy of each page | Add CRAWLER_FULL_PAGE_ARCHIVE=true to .env (uses much more disk) |
| Full-page screenshots | Add CRAWLER_FULL_PAGE_SCREENSHOT=true (also more disk) |
| Tags in your language | Add INFERENCE_LANG=german (the default is english) |
| Better tags from long pages | Raise INFERENCE_CONTEXT_LENGTH (default 2048). Bigger means better tags and more memory in Ollama |
After any .env change run docker compose up -d again. An import adds every URL in the file (you cannot pick single ones) and puts them in an automatic list.
Check it worked#
Save a page from the extension. It shows up in the web app within seconds.
Choosing the Ollama model#
The text model writes the tags, the image model describes pictures. Pick what your hardware can run.
The official docs use gemma3 and llava. You can use any model you have pulled in Ollama, but the names in the Compose file must match exactly (ollama list shows them). A bigger model gives better tags but needs more memory.
| Setting | What it does |
|---|---|
INFERENCE_TEXT_MODEL | Model for tags and summaries |
INFERENCE_IMAGE_MODEL | Vision model for image bookmarks (must support images, for example llava) |
INFERENCE_JOB_TIMEOUT_SEC | Default 30. Raise it if your Ollama has no strong GPU |
INFERENCE_ENABLE_AUTO_SUMMARIZATION | true also writes a summary per bookmark (default off) |
OLLAMA_KEEP_ALIVE | Native API only (below). How long the model stays loaded |
Native API instead? The docs call the OpenAI-compatible route (used above) the recommended one. The alternative is OLLAMA_BASE_URL=http://host.docker.internal:11434 without OPENAI_API_KEY (if both are set, OPENAI_API_KEY wins). If your model has no structured output, add INFERENCE_OUTPUT_SCHEMA=plain.
Want semantic search too? Pull an embedding model and set EMBEDDING_TEXT_MODEL=embeddinggemma, EMBEDDING_DIMENSIONS=768, EMBEDDING_CONTEXT_LENGTH=2048 and EMBEDDING_ENABLE_AUTO_INDEXING=true. This is optional. If you ever change the embedding model, all embeddings must be rebuilt.
Put it behind HTTPS (with or without Traefik)#
With Traefik#
This is for a server with a domain name and Traefik already running. Running only at home on localhost? Skip this section.
Prerequisites: a domain, DNS pointing to the server, ports 80 and 443 open, and Traefik on the external proxy network.
docker network create proxy
Change the web service like this. The labels are our example, not from the Karakeep docs (VERIFY against your Traefik setup). There is no ports: entry, so only Traefik can reach the app:
web:
image: ghcr.io/karakeep-app/karakeep:${KARAKEEP_VERSION:-release}
restart: unless-stopped
volumes:
- ./data:/data
env_file:
- .env
environment:
MEILI_ADDR: http://meilisearch:7700
BROWSER_WEB_URL: http://chrome:9222
DATA_DIR: /data
networks:
- default
- proxy
labels:
- traefik.enable=true
- traefik.docker.network=proxy
- traefik.http.routers.karakeep.rule=Host(`<your-domain>`)
- traefik.http.routers.karakeep.entrypoints=https
- traefik.http.routers.karakeep.tls=true
- traefik.http.services.karakeep.loadbalancer.server.port=3000
networks:
proxy:
external: true
Keep your Ollama lines from Step 2 in the environment block. Then set NEXTAUTH_URL=https://<your-domain> in .env and run docker compose up -d. An https address also makes the login cookies secure-only.
Without Traefik: Caddy#
Choose Caddy if you have no Traefik yet and run one small server.
| Case | Result | You do |
|---|---|---|
| Public domain | Automatic Let's Encrypt | DNS A record to the server, ports 80 and 443 open |
| Home only | tls internal (local certificate) | Trust Caddy's root certificate on each device |
Caddyfile for a public domain:
karakeep.example.com {
reverse_proxy localhost:3000
}
Caddyfile for home only (point the name at the Caddy machine):
karakeep.home.arpa {
tls internal
reverse_proxy localhost:3000
}
Trust the root certificate (pki/authorities/local/root.crt in Caddy's data directory):
# Linux: on the Caddy machine sudo caddy trust # other Linux devices, with root.crt copied over sudo cp root.crt /usr/local/share/ca-certificates/caddy-root.crt && sudo update-ca-certificates # Windows (PowerShell as administrator) certutil -addstore -f ROOT root.crt # Mac sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root.crt
Run Caddy (Linux: install from the official apt repository on caddyserver.com/docs/install first):
# Linux sudo systemctl reload caddy # Windows (PowerShell) winget install CaddyServer.Caddy caddy run # Mac brew install caddy && caddy run
Or use Docker on the app's network. Bind the app port to the host only (127.0.0.1:3000:3000) or drop ports:, add the proxy network to the web service, use reverse_proxy web:3000 in the Caddyfile and save this in ~/docker_volumes/caddy (create the network once: docker network create proxy):
services:
caddy:
image: caddy:2.11.7
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy_data:/data
- ./caddy_config:/config
networks:
- proxy
networks:
proxy:
external: true
Set NEXTAUTH_URL in .env to the https address and run docker compose up -d. Caddy passes WebSockets by default, so nothing else is needed.
Check it worked: curl -I https://karakeep.example.com answers HTTP/2 200 (or a redirect to the sign-in page).
Security and testing#
Karakeep opens web pages for you, so treat who can use it as a security decision.
- Keep it private. Do not forward port 3000 to the internet. Reach it through WireGuard or Tailscale, or use the HTTPS setup above on a server you control.
- Few users only. The official docs warn that anyone with an account can make your server fetch any web address and see the result. That can expose your IP and probe your network. Karakeep has basic SSRF protection, but it covers only some cases. Give accounts to people you trust, and keep
DISABLE_SIGNUPS=true. - Harden if you share it. The docs suggest sending the browser container's traffic through a VPN with strict rules, hosting it outside your network, or using a crawler proxy.
- Secrets in
.env. Lock the file and never commit it:
chmod 600 .env
- Ollama stays private too: no public route to port 11434.
Tests:
docker compose ps docker compose logs web --tail 100 curl -I https://<your-domain>
Expected: containers running, no errors in the log, and HTTP/2 200 (or a redirect to the sign-in page) from the last command.
Troubleshooting#
| Problem | Solution |
|---|---|
| No tags appear | Run docker compose logs web --tail 100. A line like "skipping inference as it's not configured" means a typo in the variable name, or you forgot docker compose up -d after editing |
| Tags fail with Ollama | INFERENCE_TEXT_MODEL still has the default (a GPT model). Set it to a model from ollama list |
| Ollama not reachable | localhost points to the container. Use host.docker.internal (with extra_hosts on Linux) and let Ollama listen on 0.0.0.0 |
| Tags time out on a slow machine | Raise INFERENCE_JOB_TIMEOUT_SEC above the default 30 |
| Pages are not crawled | You renamed the chrome service but not BROWSER_WEB_URL |
| Search returns nothing | MEILI_MASTER_KEY changed. Restore the old value |
| Disk grows fast | Full-page archive or screenshots are on. Turn them off or set CRAWLER_FULL_PAGE_ARCHIVE_MAX_SIZE_MB |
Exact error: Invalid Origin#
Invalid Origin
Seen on login after updating to 0.34 (GitHub issue #3215, open). Version 0.34 changed the auth library. The maintainer's answer: set NEXTAUTH_URL to exactly the address you open in the browser, including https://. Then run docker compose up -d. You can also list extra addresses in AUTH_TRUSTED_ORIGINS.
Exact error: database not initialized#
SqliteError: no such table: user
The data folder was wiped or changed, or DATA_DIR is missing from your Compose file. Keep DATA_DIR: /data and the ./data:/data mount. If you wiped it on purpose, restart the container so it rebuilds the database.
Exact error: Meilisearch version#
Your database version (x.x.x) is incompatible with your current engine version (x.x.x)
You changed the Meilisearch image tag. Use the version from the official Compose file (v1.41.0 here), or follow the official Meilisearch upgrade guide.
Harmless: Chrome DnsConfig#
The chrome log line "Failed to read DnsConfig" is benign. The docs say you can ignore it.
Known bugs#
Open on GitHub at the time of writing (read 2026-10-26):
- #3215: "Invalid Origin" after updating to 0.34 (workaround above).
- #3223: the Android app ignores the title of a shared link.
- #3221: uploading a SingleFile capture to an existing bookmark changes its "created at" date.
- If 0.34.1 misbehaves for you, roll back by setting
KARAKEEP_VERSION=0.34.0in.env.
Updates and backups#
Update by changing the version in .env, back up by copying the data and search folders.
Update (Linux/Mac; on Windows use the same docker compose lines in PowerShell):
cd ~/docker_volumes/karakeep # read the release notes, set KARAKEEP_VERSION in .env, then: docker compose pull && docker compose up -d
Updating Karakeep never changes the Meilisearch image. If you copy a newer Compose file with a newer Meilisearch tag, do the one-time database upgrade from the official guide first. The Chrome image follows release, so docker compose pull refreshes it too.
Backup script (Linux/Mac). It stops the stack briefly so the database is consistent:
mkdir -p ~/docker_volumes/karakeep/backups cd ~/docker_volumes/karakeep docker compose stop tar czf backups/karakeep-$(date +%F).tgz data meili_data .env docker-compose.yml docker compose start
Windows (PowerShell):
Set-Location "$HOME\docker_volumes\karakeep" docker compose stop Compress-Archive -Path data, meili_data, .env, docker-compose.yml -DestinationPath "backups\karakeep-$(Get-Date -Format yyyy-MM-dd).zip" -Force docker compose start
The backup holds your .env with secrets, so keep it somewhere private. Schedule: Linux/Mac cron at 03:00 (0 3 * * * <path-to-script>). Windows: Task Scheduler running the same steps in PowerShell.
Restore test#
Do this once, before you need it. It starts the backup on port 3001 next to your real instance:
mkdir -p /tmp/restore && tar xzf ~/docker_volumes/karakeep/backups/karakeep-*.tgz -C /tmp/restore cd /tmp/restore sed -i 's/3000:3000/3001:3000/' docker-compose.yml sed -i 's#^NEXTAUTH_URL=.*#NEXTAUTH_URL=http://localhost:3001#' .env docker compose -p karakeep-restore up -d
Open http://localhost:3001, sign in and check that your bookmarks are there. Then clean up:
docker compose -p karakeep-restore down && cd / && rm -rf /tmp/restore
Performance and resources#
Karakeep is light. The AI model is what needs the memory. We did not measure RAM or CPU ourselves and the docs give no official numbers, so measure on your own machine:
docker stats --no-stream
Tips:
- The Chrome container uses the most RAM while it crawls.
- Full-page archives and screenshots grow your data folder quickly. Set
CRAWLER_FULL_PAGE_ARCHIVE_MAX_SIZE_MBto skip huge pages. - A model on CPU is slow. Raise
INFERENCE_JOB_TIMEOUT_SEC. INFERENCE_NUM_WORKERS(default 1) runs more tagging jobs at once. Only raise it if Ollama has room.
FAQ#
Do I need Ollama? No. Without an AI provider Karakeep still saves, archives and searches. Tags are then made by hand.
Can I use OpenAI instead? Yes. Set OPENAI_API_KEY, remove OPENAI_BASE_URL and the Ollama model lines. The docs note that OpenAI needs pre-paid credit.
Why is the Chrome image not pinned? The project publishes it only as release, and the official Compose file uses that tag.
Is my data sent anywhere? With Ollama on your own machine, no. Pages are fetched by your Chrome container and tagged by your model.
Can I migrate from Hoarder? Karakeep was called Hoarder before. The docs have a migration page.
Sources#
- Caddy docs: automatic HTTPS, tls internal, reverse_proxy, install (Caddy 2.11.7, read 2026-10-26)
- Karakeep docs: Docker install, configuration, different AI providers, quick sharing, import, security considerations, troubleshooting (read 2026-10-26 from the repository at v0.34.1)
- Official Compose file at v0.34.1
- GitHub releases, v0.34.1 (22 Oct 2026) and issue #3215
- Package: ghcr.io/karakeep-app/karakeep
Checked against the official docs on 2026-10-26. Not run by DIY Lab Hub. Items marked VERIFY are not confirmed in the official docs.