Self-HostingDocker

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.

Karakeep official logo

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 .env file.
  • You need: Docker with the Compose plugin, a browser, and Ollama (optional, for AI tags).
WhatValue
Current versionv0.34.1, released 22 Oct 2026 (GitHub releases)
Pinned imagesghcr.io/karakeep-app/karakeep:0.34.1, ghcr.io/karakeep-app/karakeep-chrome:release, getmeili/meilisearch:v1.41.0
Port3000 (web page and API)
Official docsdocs.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.

How Karakeep works: you save a link from the extension, phone app or web page; the Karakeep web container crawls it with the Chrome container, indexes it in Meilisearch, saves it in your data folder and optionally 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.

SystemWhat to install
LinuxDocker Engine + Compose plugin; Ollama
WindowsDocker Desktop (WSL2 backend); Ollama for Windows
MacDocker Desktop; Ollama for Mac

Check your tools:

Run in terminalbash
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#

Run in terminalbash
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)#

Codepowershell
$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:

Run in terminalbash
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#

Run in terminalbash
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#

Run in terminalbash
cd ~/docker_volumes/karakeep
nano docker-compose.yml

Windows (PowerShell)#

Codepowershell
Set-Location "$HOME\docker_volumes\karakeep"
notepad docker-compose.yml

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
cd ~/docker_volumes/karakeep
nano docker-compose.yml

Paste this into docker-compose.yml on every system and save:

File contentyaml
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, chrome and meilisearch. Keep these names: BROWSER_WEB_URL and MEILI_ADDR depend on them.
  • extra_hosts makes host.docker.internal work 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_KEY to extra_hosts). Karakeep then runs without AI tags.
  • MEILI_MASTER_KEY is read from .env by both web and meilisearch. Do not change it later, or search stops working.

Check it worked#

Run in terminalbash
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#

Run in terminalbash
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:

Run in terminalbash
sudo systemctl edit ollama

Add these two lines, save, then restart:

File contenttext
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"
Run in terminalbash
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)#

Run in terminalpowershell
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)#

Run in terminalbash
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):

Run in terminalbash
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#

Run in terminalbash
cd ~/docker_volumes/karakeep
docker compose up -d

Windows (PowerShell)#

Codepowershell
Set-Location "$HOME\docker_volumes\karakeep"
docker compose up -d

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
cd ~/docker_volumes/karakeep
docker compose up -d

Check it worked#

Run in terminalbash
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.

  1. Open http://localhost:3000 and click Sign Up.
  2. Enter name, email and a strong password.
  3. Save a first link with the + button, for example a page you like.
  4. 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:

File contenttext
DISABLE_SIGNUPS=true

Apply it:

Run in terminalbash
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 wantDo this
Save from the browserInstall the Chrome extension or the Firefox add-on. Enter your server address and sign in
Save from the phoneInstall the iOS or Android app, then use the share menu. Your phone must reach the server address
Import from Chrome or FirefoxExport bookmarks as HTML in the browser, then in Karakeep go to Settings and click Import Bookmarks from HTML file
Import from Pocket or OmnivoreSettings, import. Pocket gives a CSV, Omnivore gives JSON files (see the import page)
Keep a full copy of each pageAdd CRAWLER_FULL_PAGE_ARCHIVE=true to .env (uses much more disk)
Full-page screenshotsAdd CRAWLER_FULL_PAGE_SCREENSHOT=true (also more disk)
Tags in your languageAdd INFERENCE_LANG=german (the default is english)
Better tags from long pagesRaise 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.

SettingWhat it does
INFERENCE_TEXT_MODELModel for tags and summaries
INFERENCE_IMAGE_MODELVision model for image bookmarks (must support images, for example llava)
INFERENCE_JOB_TIMEOUT_SECDefault 30. Raise it if your Ollama has no strong GPU
INFERENCE_ENABLE_AUTO_SUMMARIZATIONtrue also writes a summary per bookmark (default off)
OLLAMA_KEEP_ALIVENative 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.

Run in terminalbash
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:

File contentyaml
  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.

CaseResultYou do
Public domainAutomatic Let's EncryptDNS A record to the server, ports 80 and 443 open
Home onlytls internal (local certificate)Trust Caddy's root certificate on each device

Caddyfile for a public domain:

File contenttext
karakeep.example.com {
    reverse_proxy localhost:3000
}

Caddyfile for home only (point the name at the Caddy machine):

File contenttext
karakeep.home.arpa {
    tls internal
    reverse_proxy localhost:3000
}

Trust the root certificate (pki/authorities/local/root.crt in Caddy's data directory):

Run in terminalbash
# 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):

Run in terminalbash
# 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):

File contentyaml
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:
Run in terminalbash
chmod 600 .env
  • Ollama stays private too: no public route to port 11434.

Tests:

Run in terminalbash
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#

ProblemSolution
No tags appearRun 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 OllamaINFERENCE_TEXT_MODEL still has the default (a GPT model). Set it to a model from ollama list
Ollama not reachablelocalhost 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 machineRaise INFERENCE_JOB_TIMEOUT_SEC above the default 30
Pages are not crawledYou renamed the chrome service but not BROWSER_WEB_URL
Search returns nothingMEILI_MASTER_KEY changed. Restore the old value
Disk grows fastFull-page archive or screenshots are on. Turn them off or set CRAWLER_FULL_PAGE_ARCHIVE_MAX_SIZE_MB

Exact error: Invalid Origin#

Codetext
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#

File contenttext
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#

Codetext
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.0 in .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):

Run in terminalbash
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:

Run in terminalbash
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):

Codepowershell
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:

Run in terminalbash
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:

Run in terminalbash
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:

Run in terminalbash
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_MB to 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#

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.