How to Set Up a Local Database for Karakeep Development

To set up a local database for Karakeep development, clone the karakeep-app/karakeep repository, install dependencies using pnpm, copy .env.example to .env, configure DATABASE_URL to point to a local SQLite file, and run pnpm dev to automatically bootstrap and migrate the database.

Karakeep uses a SQLite database managed by Drizzle ORM to persist all user data, bookmarks, and metadata. This guide walks you through configuring a local development environment that mirrors production, whether you run the stack natively with Node.js or prefer containerized development with Docker Compose.

Prerequisites

Before you begin, ensure your system meets the following requirements:

  • Node.js version 20.x or higher
  • pnpm version 9.x (the monorepo uses pnpm-workspace.yaml for dependency management)
  • Git for cloning the repository
  • (Optional) Docker and Docker Compose version 2.20+ if you prefer a containerized setup

Step-by-Step Configuration

Clone the Repository and Install Dependencies

Start by cloning the repository and installing all monorepo dependencies:

git clone https://github.com/karakeep-app/karakeep.git
cd karakeep
pnpm install

This installs the full dependency tree, including better-sqlite3 and Drizzle ORM packages referenced in [packages/db/drizzle.ts](https://github.com/karakeep-app/karakeep/blob/main/packages/db/drizzle.ts).

Configure Environment Variables

Create a local environment file from the provided template:

cp .env.example .env

Edit .env and set the DATABASE_URL variable to specify where the SQLite file should reside:


# .env

DATABASE_URL=sqlite://./dev.db

Karakeep parses this connection string in [packages/db/drizzle.ts](https://github.com/karakeep-app/karakeep/blob/main/packages/db/drizzle.ts) to instantiate the better-sqlite3 driver. The path can be relative to the repository root or an absolute filesystem path.

Initialize the Database

Run the development server to trigger automatic migration:

pnpm dev

On first startup, the application executes the migrate() function imported from drizzle-orm/better-sqlite3, which applies all pending SQL migrations located in packages/db/drizzle/*.sql. You will see log output confirming the database is ready and the web server is listening on http://localhost:3000.

Understanding the Database Architecture

Drizzle ORM Bootstrap

The database connection is centralized in [packages/db/drizzle.ts](https://github.com/karakeep-app/karakeep/blob/main/packages/db/drizzle.ts). This file exports a singleton db object created via:

import { drizzle } from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3";

const sqlite = new Database(process.env.DATABASE_URL!);
export const db = drizzle(sqlite, { schema });

This singleton is imported by all tRPC routers and background workers, ensuring every component speaks to the same SQLite file.

Schema and Migrations

Table definitions reside in [packages/db/schema.ts](https://github.com/karakeep-app/karakeep/blob/main/packages/db/schema.ts), which provides the type-safe schema passed to the Drizzle client. Incremental migrations are stored as plain SQL files under packages/db/drizzle/ (e.g., 0084_rule_engine_multi_list_support.sql). The migrate() utility runs these scripts transactionally on startup, so you never need to manually execute SQL dumps.

Optional: Docker Compose Development Stack

For a fully isolated environment that includes Meilisearch and background workers, use the provided development compose file:

docker compose -f docker/docker-compose.dev.yml up

The [docker/docker-compose.dev.yml](https://github.com/karakeep-app/karakeep/blob/main/docker/docker-compose.dev.yml) mounts your local SQLite file (as defined by DATABASE_URL) into the web, workers, and api services. This ensures data persists across container restarts while keeping the setup identical to native development.

Verifying the Setup

Access the Database Programmatically

You can query the database directly using the exported Drizzle client in any server-side context:

// Example: Query bookmarks in a tRPC router
import { eq } from "drizzle-orm";
import { db } from "@karakeep/db/drizzle";
import { bookmarks } from "@karakeep/db/schema";

const userBookmarks = await db
  .select()
  .from(bookmarks)
  .where(eq(bookmarks.userId, "user-123"));

Launch Drizzle Studio

To inspect tables and run ad-hoc queries via a web UI:

pnpm run db:studio

This command launches Drizzle Studio on http://localhost:4000, providing a visual interface to the same SQLite file defined in your .env.

Troubleshooting Common Issues

  • SQLITE_CANTOPEN error on startup: The directory path in DATABASE_URL does not exist or lacks write permissions. Ensure the parent directory exists and is writable by the Node.js process.
  • Migrations not applied: An existing dev.db file was created before migration files were added. Delete the database file and restart pnpm dev to force a full migration run.
  • Type errors when importing db: The @karakeep/db workspace alias is not linked. Run pnpm install again to regenerate monorepo symlinks.
  • Docker containers cannot find the database: Verify that the volume mount in docker-compose.dev.yml points to the same absolute path specified in your .env file.

Summary

  • Karakeep uses SQLite with Drizzle ORM for zero-configuration local development.
  • Configure the database path via DATABASE_URL in .env (copied from .env.example).
  • The db singleton exported from [packages/db/drizzle.ts](https://github.com/karakeep-app/karakeep/blob/main/packages/db/drizzle.ts) handles connections and automatic migrations.
  • Run pnpm dev to bootstrap the database and start the development server.
  • Use pnpm run db:studio to explore the schema and data via a web interface.
  • Docker Compose provides an alternative setup with the same SQLite backing store.

Frequently Asked Questions

What database engine does Karakeep use for local development?

Karakeep uses SQLite via the better-sqlite3 driver. The connection is managed by Drizzle ORM, which parses the DATABASE_URL environment variable to locate the .db file on disk. This eliminates the need for a separate PostgreSQL or MySQL server during development.

How do I reset my local database to a clean state?

Delete the SQLite file specified in your DATABASE_URL (e.g., rm ./dev.db) and restart the application with pnpm dev. On restart, Drizzle will recreate the file and re-run all migrations from packages/db/drizzle/*.sql, giving you a fresh schema and empty tables.

Can I use PostgreSQL instead of SQLite for local development?

While the production deployment can target PostgreSQL, the default local development setup is optimized for SQLite. Switching to PostgreSQL would require modifying packages/db/drizzle.ts to use the drizzle-orm/postgres-js driver and adjusting the DATABASE_URL format accordingly, which is not covered by the standard development documentation.

Where are the database migration files stored?

All migration SQL files are located in the packages/db/drizzle/ directory (e.g., [packages/db/drizzle/0084_rule_engine_multi_list_support.sql](https://github.com/karakeep-app/karakeep/tree/main/packages/db/drizzle)). These files are executed automatically by the migrate() function in [packages/db/drizzle.ts](https://github.com/karakeep-app/karakeep/blob/main/packages/db/drizzle.ts) when the application starts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →