You have fifteen years of links scattered across three browsers, a read-later service that already shut down, and a folder of emails to yourself. Karakeep, formerly Hoarder, keeps all of them on your own disk, archives the whole page in case it disappears, and tags them with a language model. This guide installs v0.33.2 from the official compose file, measures what it really costs in disk, and sets up tagging with OpenAI and with a local Ollama.

Key takeaways

  • The official compose file starts three containers: the app on port 3000, a headless Chrome for crawling, and Meilisearch v1.41.0 for search.
  • Full page archival is done by monolith and video archival by yt-dlp, both bundled in the image. Both are off by default, and for good reason.
  • Measured on the test machine: one bookmark with full archive, PDF and screenshot takes about 1.3 MB. The files outweigh the database nine to one.
  • AI tagging is optional. With OpenAI it costs cents; with a local model on CPU it is a batch job, not an interactive feature.
  • The backup that matters is the whole data volume, not just the SQLite file: the archived assets live inside it.

Karakeep describes itself in its documentation as an open source bookmark-everything app that uses AI to automatically tag the content you throw at it. In practice it takes four things: links, text notes, images and files such as PDFs. The repository, opened in February 2024 and published under AGPL-3.0, has 28,670 stars and 1,468 forks as of late August 2026, with 180 people having contributed code.

Diagram of a link's journey through Karakeep: the saved URL goes through headless Chrome, is archived with monolith and yt-dlp, and ends up indexed in Meilisearch with AI tags.

The interesting part happens after you paste the address. The crawler opens the page in a headless Chrome, extracts metadata and readable text, and stores a screenshot. If you ask for it, it also saves a complete local copy made with monolith[1], which packs the page with its images and styles into a single HTML file.

If the page carries video, yt-dlp[2] downloads it. If you save an image, tesseract.js runs OCR over it and the extracted text goes into the index too. Everything ends up in Meilisearch, which is what keeps search fast as the collection grows.

Checked inside the running image: monolith 2.10.1, yt-dlp 2026.07.04 and Node v24.18.1. These are not dependencies you install yourself; the build file compiles monolith from Rust and downloads the yt-dlp binary.

How much disk archival actually costs

This is the part almost nobody checks before importing ten thousand bookmarks. The documentation warns you in the row for the relevant variable: "Whether to store a full local copy of the page or not. Disabled by default, as it can lead to much higher disk usage. If disabled, only the readable text of the page is archived". Which means: leave it off and you only keep readable text, and readable text weighs nothing.

I turned on CRAWLER_FULL_PAGE_ARCHIVE and CRAWLER_STORE_PDF and saved real pages to measure it. The breakdown for a single bookmark, the Spanish Wikipedia article on web bookmarks:

Generated file Size
Full copy from monolith 1,177,803 bytes
Screenshot 136,587 bytes
PDF snapshot 86,079 bytes
Extracted readable text 12,938 bytes
Banner image 1,134 bytes

One bookmark, 1.4 MB. The Docker Compose documentation page came out similar: 962,341 bytes of archive, 126,607 of screenshot and 120,402 of PDF. With five bookmarks saved, the data volume held 6.1 MB, of which 5.5 MB were archived files and only 626,688 bytes the SQLite database. The ratio is nine to one.

Do the arithmetic before importing: five thousand bookmarks with full archival land around 6.5 GB, and every downloaded video adds up to 50 MB, which is the default of CRAWLER_VIDEO_DOWNLOAD_MAX_SIZE. With archival off, the same bookmark stays around 150 KB, because the screenshot is on out of the box. My recommendation after seeing the numbers: keep the full archive on and the PDF off, because the PDF is a second copy of the same thing with worse results.

Installing Karakeep with Docker Compose

Installation is four steps and no tricks:

  1. Create a directory for the install and download the official compose file into it.
  2. Generate the two secrets with openssl rand -base64 36, one per variable.
  3. Write the .env file with those four minimum lines.
  4. Bring the stack up and open port 3000 to create the first account.

The download points at the repository:

mkdir karakeep-app && cd karakeep-app
wget https://raw.githubusercontent.com/karakeep-app/karakeep/main/docker/docker-compose.yml

The minimum variables file is four lines. Copy the name carefully: the variable is MEILI_MASTER_KEY, and per the project’s own configuration schema it is the one read by both the Meilisearch container and Karakeep’s search client.

KARAKEEP_VERSION=release
NEXTAUTH_SECRET=string_generated_with_openssl
MEILI_MASTER_KEY=another_string_generated_with_openssl
NEXTAUTH_URL=http://localhost:3000

## Archival (optional, read the disk section first)
CRAWLER_FULL_PAGE_ARCHIVE=true
CRAWLER_VIDEO_DOWNLOAD=true

KARAKEEP_VERSION=release follows the latest stable release. If you would rather control when you upgrade, pin the version number and bump it by hand. Then the usual command:

docker compose up -d

One detail from v0.33.2: the Chrome container changed image. It is no longer the old alpine-chrome, abandoned by its maintainer, but ghcr.io/karakeep-app/karakeep-chrome:release, built on chromedp’s headless-shell project and shipping Chrome 151 instead of the previous 124. If you copied a compose file from an old article, that is the line to change.

What the first startup looks like

I deployed the stack on an arm64 machine with 18 cores to write this. The log makes clear that the app and the background workers live in the same container, supervised by s6:

s6-rc: info: service svc-workers: starting
s6-rc: info: service svc-web: starting
Next.js 16.2.12
info: Workers version: 0.33.2
info: [crawler] Loading adblocker ...
info: Starting inference worker ...
info: [Crawler] Connecting to existing browser instance: http://chrome:9222/

The deployment figures, so you know what you are getting into. The arm64 images weigh 2.08 GB for the app, 517 MB for Chrome and 261 MB for Meilisearch. Idle, with no bookmarks, measured memory was 681.6 MiB for the app, 204.7 MiB for Chrome and 85.2 MiB for Meilisearch: close to 972 MiB across the three. This does not fit on a two-gigabyte board with other things running.

The first account to register becomes an administrator automatically. If you plan to expose the instance, set DISABLE_SIGNUPS=true right after creating yours.

AI tagging: hosted model or local model

With no provider configured, the log says exactly this: [inference] No inference client configured, nothing to do now. Tagging is optional and it does not fail silently, it does not happen.

The hosted route is one line. The default model is gpt-5.6-luna for text and gpt-4o-mini for images. The project publishes what it costs: tags for over 6,000 bookmarks for less than a dollar, and over 1,000 images for less than another.

OPENAI_API_KEY=sk-...

The local route is the one that matters here, and it has two variants. The one the documentation recommends uses the OpenAI-compatible endpoint that Ollama exposes, because it copes better with models that format messages their own way:

OPENAI_API_KEY=ollama
OPENAI_BASE_URL=http://ollama.mylab.local:11434/v1
INFERENCE_TEXT_MODEL=gemma3
INFERENCE_IMAGE_MODEL=llava
EMBEDDING_TEXT_MODEL=embeddinggemma
EMBEDDING_DIMENSIONS=768
INFERENCE_LANG=spanish

The native variant uses OLLAMA_BASE_URL and carries a warning in capitals in the documentation: "MAKE SURE YOU DON’T HAVE OPENAI_API_KEY set, otherwise it takes precedence". If the model you pick does not support structured output, add INFERENCE_OUTPUT_SCHEMA=plain. And never put localhost in that address: inside the container it points at the container itself, not at the host. If you do not have Ollama running yet, start with the Ollama install guide.

Now the honest part, because I tested it. I wired up an Ollama running gemma3 at 4 billion parameters and let it tag a Wikipedia article. It took 259 seconds on pure CPU with 18 cores pinned, for 1,109 tokens. Ollama’s own counter confirms it: 250,063 milliseconds.

The result was twenty tags in English, with INFERENCE_LANG=spanish configured, when the program’s own prompt asks for ten to fifteen tags and states that they must be in the given language.

Two readings from that. First, a 4-billion-parameter model ignores fine instructions, so running locally means moving to a bigger model and accepting that you need a graphics card. Second, INFERENCE_NUM_WORKERS defaults to 1, so tagging runs serially: importing ten thousand bookmarks and tagging them locally is a matter of days, not an afternoon. As a background task it works; as an instant response when you save a link, it does not.

Importing what you already have, and capturing what is new

The importer takes the Netscape HTML format, which is what Chrome and Firefox export, Pocket’s new CSV, and Omnivore’s JSON files. Titles, tags and creation dates are preserved, and everything imported goes into a list created for the occasion. One warning from the documentation worth reading twice: every address in the file gets imported, with no way to pick and choose.

For new material there are three routes. The official Chrome and Firefox extensions add a save button. The native iOS and Android apps show up in the system share menu and support offline reading.

RSS feeds turn this into something else: you register a feed in the settings, Karakeep checks it every hour and creates a bookmark per new entry, skipping duplicates. With that, a blog you follow gets archived in full without you doing anything, which is exactly the case where full archival earns its disk.

What you have to copy when you take a backup

The usual mistake is backing up the database and calling it done. DATA_DIR holds two things: the db.db file and the assets directory, and we have already seen that the second weighs nine times the first. The useful backup is the whole data volume, with the container stopped or the database in a consistent state.

The Meilisearch volume is the pleasant exception: you do not need to back it up. If you lose it, you empty the data.ms folder and rebuild it from the admin panel with the reindex-all-bookmarks job. Be careful upgrading Meilisearch on your own, though, because the version is pinned at 1.41.0 and a version jump leaves the index unreadable.

Since v0.33 there is also a per-user backup worker that produces a scheduled export file. It is useful for taking your data with you, with one caveat worth being clear about. That file is stored as another asset inside the same data directory, so it is an export, not an off-box backup. If you already run Nextcloud with Docker, the natural destination for those volumes is already solved.

Karakeep versus Linkwarden and browser bookmarks

Linkwarden is the inevitable comparison: same AGPL-3.0 licence, same language, same Meilisearch underneath, and 19,631 stars. The difference you notice on day one is the database.

Karakeep v0.33.2 Linkwarden Browser bookmarks
Database SQLite in one file Separate PostgreSQL 16 None
Containers 3 3 0
Page archival monolith, PDF and screenshot Yes No
Video via yt-dlp Yes No No
Tagging with a local model Yes, via Ollama Partial No
Full text search Meilisearch Meilisearch Title only
Disk cost High High Zero

Against browser bookmarks the comparison is simpler than it looks. A browser bookmark stores an address; Karakeep stores the page. The day the page disappears, one leaves you a headstone and the other the content. That is the whole argument, and it is what justifies the gigabyte of memory and the six gigabytes of disk.

If what you want to archive is your own documents rather than other people’s pages, this is not the tool: Paperless-ngx does that job with its own OCR and classification. Plenty of us end up with both, and they overlap less than you would think.

Frequently asked questions

Can I install Karakeep with no AI at all?

Yes, and it works fine. Without OPENAI_API_KEY or OLLAMA_BASE_URL the inference worker logs that no client is configured and moves on. You lose automatic tags and summaries, but you keep crawling, archival, OCR and full text search through Meilisearch.

Which local model should I pick for tagging?

The documentation’s examples use gemma3 for text and llava for images. With a 4-billion-parameter model on CPU I measured 259 seconds per bookmark, and English tags despite asking for Spanish. Plan it as a nightly job, or put a graphics card in the way.

How much disk do I need for ten thousand bookmarks?

With full archival on, roughly 13 GB based on what I measured here, plus whatever the videos add at up to 50 MB each. With archival off you drop to a little over 1.5 GB, because only the screenshot and the readable text get stored.

Conclusion

Installing Karakeep is downloading a compose file, writing four variables and starting three containers. The installer never asks two things you have to decide beforehand. The first is how much disk you are willing to spend archiving whole pages. The second is whether you will pay for tagging in cents to a provider or in hours of your own CPU.

My reading after measuring it is that full archival earns every byte, and that local tagging works better as a nightly process than as instant magic. The Spanish version of this guide is at Cómo instalar Karakeep.

Sources

  1. monolith
  2. yt-dlp
  3. Karakeep, introduction and features
  4. karakeep-app/karakeep, repository and release notes
  5. Karakeep, Docker installation
  6. Karakeep, environment variables
  7. Karakeep, configuring different AI providers
  8. Karakeep, tagging costs
  9. Karakeep, import your library
  10. Karakeep, RSS feeds
  11. Meilisearch, documentation