Appendix A: Installation and Runtime
This appendix collects setup material that would otherwise interrupt the main chapters. Use Chapter 2 for the first embedded session. Use this appendix when you need platform requirements, package coordinates, server startup commands, dtlv, Docker, Babashka, MCP, or troubleshooting notes.
1. Choose a Mode
Datalevin can be used as an embedded database, a standalone server, a server with read-only replicas, a high-availability server cluster, a command-line tool, a Babashka pod, or a local MCP tool server. The same core concepts appear in each mode: a path or URI names the database, schema describes attribute behavior, transactions add or retract facts, and Datalog queries read through a current database state.
Most readers should start with embedded mode. It has the fewest moving parts and uses the same transactions, schema, and Datalog queries as the other modes.
| Mode | Start here when | What you need first |
|---|---|---|
| Embedded library | One application process owns the database path. | Java 21+ and the language package. |
| Standalone server | Multiple services or machines need a shared database endpoint. | dtlv or the standalone JVM jar. |
dtlv CLI |
You want a REPL, shell scripts, import/export, backup, or maintenance commands. | The dtlv executable. |
| Babashka pod | You write fast Clojure scripts with bb. |
Babashka and either the pod release or local dtlv. |
| MCP server | An AI client or agent needs local database tools. | dtlv and an MCP-compliant client. |
| Replication or HA | You need read scaling or automatic failover. | Server mode, WAL, and Chapter 22. |
2. Supported Platforms
For local embedded storage, the standalone JVM jar, and pre-built dtlv command-line/server binaries, Datalevin supports the following host platforms:
| Operating system | Architecture |
|---|---|
| Windows | x86_64 / AMD64 |
| macOS | ARM64 / AArch64 |
| Linux | x86_64 / AMD64 |
| Linux | ARM64 / AArch64 |
The language libraries bundle native Datalevin components for these platforms. If your application must run on a different local platform, prefer server mode with Datalevin running on a supported host, or build and package the native components for that platform yourself.
Core Datalog and KV support is not always the same as support for every optional native feature. Vector and embedding search depend on vector-index native code; they are supported on Linux x86_64, Linux ARM64, and macOS ARM64, while Windows support is experimental.
3. Runtime Requirements
All language libraries load a JVM Datalevin runtime and require Java 21 or newer. Some language bindings have their own host-runtime requirements:
- Python: Python 3.10+ and Java 21+
- Node.js: Node.js 20+ and Java 21+
For Clojure and Java applications that load Datalevin in-process, pass these JVM flags to the JVM:
--add-opens=java.base/java.nio=ALL-UNNAMED
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED
--enable-native-access=ALL-UNNAMED
Python and Node.js bindings add the required JVM flags automatically. The --enable-native-access=ALL-UNNAMED flag suppresses native-access warnings on newer Java releases.
For Clojure CLI, put the flags in an alias:
{:aliases
{:datalevin/jvm
{:jvm-opts ["--add-opens=java.base/java.nio=ALL-UNNAMED"
"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED"
"--enable-native-access=ALL-UNNAMED"]}}}
Then run your program or REPL with that alias:
clojure -M:datalevin/jvm
For Leiningen, add top-level :jvm-opts in project.clj:
:jvm-opts ["--add-opens=java.base/java.nio=ALL-UNNAMED"
"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED"
"--enable-native-access=ALL-UNNAMED"]
For Java launch commands, pass the same options before -cp or -jar:
java --add-opens=java.base/java.nio=ALL-UNNAMED \
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED \
--enable-native-access=ALL-UNNAMED \
-cp target/classes:your-dependencies.jar your.main.Class
4. Native Dependencies
Datalevin bundles its non-JVM native libraries for the supported platforms.
If loading fails with java.lang.UnsatisfiedLinkError, first confirm that the system libc for your OS is present. Some Linux distributions may not include it by default.
If bundled OpenMP/vector math libraries do not work on your machine, install the platform packages directly. On Linux, Datalevin needs OpenMP and vectorized math support from GCC; on Debian or Ubuntu, libgomp1 is usually enough, while g++-12 gcc-12 is a useful fallback on systems with older or incomplete GCC runtime libraries:
# Debian/Ubuntu
sudo apt-get install libgomp1
# Debian/Ubuntu fallback
sudo apt-get install g++-12 gcc-12
# macOS
brew install libomp llvm
5. Embedded Language Packages
Use the language-specific documentation and package pages below for API details, current package metadata, and additional examples:
- Clojure: Clojure API documentation on cljdoc.
- Java: JavaDoc, plus Java server JavaDoc for the optional in-process server artifact.
- Python: PyPI package page for
datalevin. - Node.js: npm package page for
datalevin-node.
The default Java artifact, org.datalevin:datalevin-java, is for embedded database use and dtlv:// client connections. Java applications that need to host a Datalevin server in-process should add the separate org.datalevin:datalevin-java-server artifact. Python and Node.js packages are embedded/client libraries: they can open local databases and connect to dtlv:// servers, but they do not host a Datalevin server process themselves. Run dtlv, Docker, the standalone JVM jar, or the Java server artifact when an application needs to provide the server process.
For the current cross-language API surface, use Datalevin's language compatibility matrix as the source of truth. This guide does not reproduce that matrix.
API parity is broad, but not perfectly identical. Clojure, Java, Python, and JavaScript all expose the main Datalog connection, query, pull, explain, transaction, async transaction, listener, simulated transaction, datom/index read, bulk load, Datalog-backed KV, re-index, full-text, vector, idoc, search writer, UDF, snapshot, WAL, remote client, replica, and HA administration surfaces. The examples in the main chapters therefore use the same conceptual API across languages: transaction data goes in, queries and pull patterns come out, and operational APIs are available without dropping to a raw JSON/exec escape hatch.
For quick orientation, the main areas look like this:
| Area | Cross-language status |
|---|---|
| Datalog basics | Clojure, Java, Python, and JavaScript all support open, query, pull, explain, transactions, async transactions, listeners, simulated transactions, datoms, index reads, bulk load, Datalog-backed KV, and re-index. |
| KV APIs | All four support ordinary KV operations, list DB operations, range helpers, raw-buffer scans, stats, copy, sync, environment flag updates, KV transaction callbacks, and explicit transaction timeouts. |
| Search/vector/idoc | All four support Datalog full-text, custom analyzers through UDFs, vector search, idoc schema/options, standalone search/vector indexes, search index writers, and search re-indexing. |
| Operations | All four support backup/copy, sync, snapshots, tx log watermarks and GC, tx log inspection, remote clients, replica status, and HA membership administration. |
| UDFs and data helpers | All four support UDF registries, query/predicate/transaction UDFs, analyzer UDFs, EDN helpers, and schema/transaction construction helpers where the host language needs them. |
The remaining notable differences to keep in mind while reading are:
- Clojure, Java, and Python expose the Datalog transaction callback API (
with-transaction,withTransaction, andwith_transaction), including per-call timeout options. JavaScript does not expose that Datalog callback; use a singleconn.transact(...),conn.transactAsync(...), KV transaction APIs, or move the callback logic into a transaction function or application command. - Existing entity objects are transactable only in Clojure. Java, Python, and JavaScript support lazy entity reads, but staged entity-object mutation should be written as transaction maps, datom forms, or binding transaction builders.
This guide targets the Datalevin 1.0.0 release, the same target shown in Chapter 22. The dependency examples below use 1.0.0 where a package manager, Babashka pod, or standalone-jar filename requires a released version string.
Some examples below use the shortest install form, such as pip install datalevin, npm install datalevin-node, brew install, scoop install, or docker pull huahaiy/datalevin. Treat those as convenience commands. For production, follow Chapter 22's version-pinning rule: pin the Datalevin version for the application, server, CLI, Docker image, Babashka pod, and language binding. Use package-manager version syntax or lockfiles for Python and Node.js, a fixed image tag for Docker, and a downloaded release artifact, standalone jar, or otherwise pinned dtlv package for CLI/server deployments. Do not let production hosts float to whatever Homebrew, Scoop, or the latest Docker tag currently resolves to.
For Clojure, datalevin/datalevin is the full library. Use it when the same process needs server, CLI, or Babashka pod runtime code, including the datalevin.server namespace shown later in this appendix. Use org.datalevin/datalevin-embedded when an application only needs the local embedded APIs and datalevin.client.
;; Full library with Clojure CLI
{:deps {datalevin/datalevin {:mvn/version "1.0.0"}}}
;; Full library with Leiningen
:dependencies [[datalevin "1.0.0"]]
;; Embedded-only artifact with Clojure CLI
{:deps {org.datalevin/datalevin-embedded {:mvn/version "1.0.0"}}}
;; Embedded-only artifact with Leiningen
:dependencies [[org.datalevin/datalevin-embedded "1.0.0"]]
<!-- Maven pom.xml -->
<dependency>
<groupId>org.datalevin</groupId>
<artifactId>datalevin-java</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Add only when hosting a Datalevin server inside a Java process. -->
<dependency>
<groupId>org.datalevin</groupId>
<artifactId>datalevin-java-server</artifactId>
<version>1.0.0</version>
</dependency>
// Gradle Kotlin DSL
repositories {
mavenCentral()
}
dependencies {
implementation("org.datalevin:datalevin-java:1.0.0")
// Optional in-process server hosting API.
implementation("org.datalevin:datalevin-java-server:1.0.0")
}
# Python 3.10+
python -m pip install datalevin
// Node.js 20+
npm install datalevin-node
6. Install dtlv
Datalevin's native image is a command-line program called dtlv. It can query, transact, import, export, back up, compact, and administer Datalevin databases. The same executable can also run a server or an interactive Datalevin REPL.
Pre-built dtlv distributions are available for Windows AMD64, macOS ARM64, Linux AMD64, and Linux ARM64.
On macOS or Linux with Homebrew:
brew install huahaiy/brew/datalevin
On Windows with Scoop:
scoop bucket add scoop-clojure https://github.com/littleli/scoop-clojure
scoop bucket add extras
scoop install datalevin
Direct release downloads are available from GitHub. Download the archive for your platform, unzip it, and put the dtlv executable on your PATH:
- macOS ARM64:
https://github.com/datalevin/datalevin/releases/download/1.0.0/dtlv-1.0.0-macos-14-aarch64.zip - Linux AMD64:
https://github.com/datalevin/datalevin/releases/download/1.0.0/dtlv-1.0.0-ubuntu-22.04-amd64.zip - Linux ARM64:
https://github.com/datalevin/datalevin/releases/download/1.0.0/dtlv-1.0.0-ubuntu-24.04-arm-aarch64.zip - Windows AMD64:
https://github.com/datalevin/datalevin/releases/download/1.0.0/dtlv-1.0.0-windows-amd64.zip
With Docker:
docker pull huahaiy/datalevin
The Docker image exposes the same dtlv command surface. To start a local server from Docker:
docker run --rm \
-p 8898:8898 \
-e DATALEVIN_DEFAULT_PASSWORD=secret \
-v "$PWD/dtlv-data:/data" \
huahaiy/datalevin --host 0.0.0.0 -r /data -p 8898 serv
The standalone JVM jar is another downloadable command-line and server runtime. It is the better choice when you want HotSpot's JIT and garbage collectors for a long-running server, or when no native dtlv build exists for your platform. Download it from:
https://github.com/datalevin/datalevin/releases/download/1.0.0/datalevin-1.0.0-standalone.jar
Then run it with the JVM flags from Section 3:
java --add-opens=java.base/java.nio=ALL-UNNAMED \
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED \
--enable-native-access=ALL-UNNAMED \
-jar datalevin-1.0.0-standalone.jar
7. Standalone Server Mode
Server mode is the right choice when multiple clients or services need a shared database endpoint.
You can run a Datalevin server either as a JVM standalone jar or a GraalVM native image. For production use, the JVM standalone jar is recommended: HotSpot's JIT and mature garbage collectors are well suited to long-running server workloads and can outperform a native image over time.
For getting started, start a local server with dtlv:
DATALEVIN_DEFAULT_PASSWORD=secret dtlv -r /path/to/data -p 8898 serv
By default the server listens on 127.0.0.1:8898. Use --host to bind another address. Binding to a non-loopback address such as 0.0.0.0 requires DATALEVIN_DEFAULT_PASSWORD; exposing the server beyond localhost must not rely on an implicit or empty administrative credential.
Server authentication starts with a built-in administrative user named datalevin. Its default password is datalevin; setting DATALEVIN_DEFAULT_PASSWORD when the server starts changes that password for the built-in account, and is required when binding the server to a non-loopback address. Treat datalevin as an administration/bootstrap account. For normal applications, create separate users and roles, then connect with those credentials. In dtlv:// URIs, the username and password are URL components, so percent-encode special characters such as @, :, /, ?, #, and %.
You can start the same server from the standalone JVM jar:
DATALEVIN_DEFAULT_PASSWORD=secret \
java --add-opens=java.base/java.nio=ALL-UNNAMED \
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED \
--enable-native-access=ALL-UNNAMED \
-jar datalevin-1.0.0-standalone.jar \
--host 0.0.0.0 -r /data/dtlv serv
In Clojure applications that depend on the full Datalevin library rather than the embedded-only artifacts, server mode can also be started in the host process with datalevin.server/create, datalevin.server/start, and datalevin.server/stop.
Java applications can do the same with the opt-in org.datalevin:datalevin-java-server artifact, which provides datalevin.DatalevinServer.
(require '[datalevin.server :as srv])
(def server
(srv/create {:host "127.0.0.1"
:port 8898
:root "/data/dtlv"}))
(srv/start server)
;; Later, during application shutdown:
(srv/stop server)
import datalevin.DatalevinServer;
import java.util.Map;
try (DatalevinServer server = DatalevinServer.create(Map.of(
":host", "127.0.0.1",
":port", 8898,
":root", "/data/dtlv",
":verbose", true))) {
server.start();
// Connect clients with dtlv:// URIs while the server is running.
}
For production deployment, dtlv, Docker, or the standalone JVM jar remains a cleaner operational boundary when the server should live independently from the application process.
8. Connect to a Server
Remote databases use dtlv:// URIs:
(require '[datalevin.core :as d])
(def conn
(d/get-conn "dtlv://datalevin:secret@localhost:8898/mydb"))
import datalevin.Connection;
import datalevin.Datalevin;
Connection conn =
Datalevin.getConn("dtlv://datalevin:secret@localhost:8898/mydb");
from datalevin import connect
conn = connect("dtlv://datalevin:secret@localhost:8898/mydb")
import { connect } from "datalevin-node";
const conn = await connect("dtlv://datalevin:secret@localhost:8898/mydb");
The URI shape is:
dtlv://<user>:<pass>@<host>:<port>/<db-name>?store=<datalog-or-kv>
The examples above use dtlv://datalevin:secret@... because the server commands in this appendix set DATALEVIN_DEFAULT_PASSWORD=secret. If you omit that variable for a localhost-only development server, the built-in account's default password is datalevin.
The store parameter defaults to datalog; use store=kv for the direct key-value store. Database names must be unique within a server.
Once connected to a server, the same transaction and query APIs used in embedded mode can be used with the remote connection.
9. Smoke Test the Server
A quick smoke test with dtlv exec is:
dtlv exec <<'EOF'
(require '[datalevin.core :as d])
(def conn (d/get-conn "dtlv://datalevin:secret@localhost:8898/getting-started"))
(d/transact! conn [{:user/name "Server Alice"}])
(d/q '[:find [?name ...] :where [_ :user/name ?name]] (d/db conn))
(d/close conn)
EOF
The query should return ["Server Alice"], possibly with set/vector rendering depending on the client surface. In Clojure, prefer (d/db conn) when a read API needs the current Datalog DB object.
10. Replication and High Availability
Datalevin has two distinct server-side replication modes.
For simple read scaling without automatic failover, configure a non-HA async read-only replica with :replica/read-only? true and :replica/source. The primary database must have WAL enabled. The replica bootstraps from the primary copy interface, tails durable WAL records, serves normal reads, and rejects user writes.
For failover, use consensus-lease HA. Think of it as one writable server at a time, with followers keeping copies and serving reads. If the writer fails, a follower can be promoted only after coordination confirms that it is safe to accept writes. HA databases require WAL, use the :strict WAL durability profile by default, and treat membership changes as explicit operator actions through datalevin.client/ha-update-membership!.
Chapter 22 covers the details: server behavior, client tuning, async read replicas, the Raft-backed control plane, bounded leases, replica lag checks, fencing hooks that stop stale leaders from accepting writes, and HA operations.
11. Command Line and Scripts
dtlv can be used in shell scripts directly. It supports common patterns such as pipes and heredocs for standard-I/O transactions and queries. The command line tool uses the same Clojure-oriented Datalevin API as embedded mode.
For a copy-paste local CLI example:
dtlv exec <<'EOF'
(require '[datalevin.core :as d])
(def conn (d/get-conn "/tmp/dtlv-cli"))
(d/transact! conn [{:msg "Hello from dtlv"}])
(d/q '[:find [?m ...] :where [_ :msg ?m]] (d/db conn))
(d/close conn)
EOF
This creates a local database under /tmp/dtlv-cli, writes one fact, queries it, and closes the connection.
12. Babashka Pod
For scripting, Babashka (bb) is a fast-starting Clojure runtime. When a database becomes necessary in a Babashka script, Datalevin can serve as a pod:
;; save as script.clj
(require '[babashka.pods :as pods])
(pods/load-pod 'huahaiy/datalevin "1.0.0")
(require '[pod.huahaiy.datalevin :as d])
(let [conn (d/get-conn "/tmp/bb-db")]
(d/transact! conn [{:msg "Hello from Babashka"}])
(println (d/q '[:find ?m :where [_ :msg ?m]] (d/db conn))))
Run it with:
bb script.clj
If the dtlv executable is already on your PATH, Babashka can load that local binary directly:
(require '[babashka.pods :as pods])
(pods/load-pod "dtlv")
(require '[pod.huahaiy.datalevin :as d])
Pod mode is intended to have parity with embedded Clojure for the public Datalevin APIs used in this guide. When testing examples, keep the pod and embedded library on the same Datalevin version; using pods/load-pod "dtlv" is a good way to test the exact local binary you downloaded. If a snippet works in embedded Clojure but fails in the pod at the same version, treat that as a bug or packaging issue rather than a semantic difference. Datalevin does not store nil values; pod mode should report the same validation failure as embedded mode, not crash.
The pod also provides defpodfn, which lets a script define a custom function and call it from a query:
(d/defpodfn greeting [name] (str "hello " name))
(d/q '[:find ?message
:where [(greeting "world") ?message]])
;; => #{["hello world"]}
13. MCP Server
For local AI agent applications and MCP-compliant clients, dtlv mcp runs a Datalevin MCP server over stdio:
dtlv mcp
dtlv --allow-writes mcp
Read-only mode is the default. Write tools must be enabled explicitly. The MCP process can open local databases directly or use dtlv:// URIs behind the local stdio process.
13.1 Codex
Codex can launch MCP servers from ~/.codex/config.toml. To make the Datalevin tools available in read-only mode, add a server definition:
[mcp_servers.datalevin]
command = "dtlv"
args = ["mcp"]
Restart Codex after changing the config. You can then ask Codex to inspect a local Datalevin database path or a remote dtlv:// database through the MCP tools:
Use the Datalevin MCP server to open /path/to/mydb and list the schema attributes.
Keep the default server read-only for exploration. If you want Codex to run transactions, configure a separate, explicitly named writable server:
[mcp_servers.datalevin-writable]
command = "dtlv"
args = ["--allow-writes", "mcp"]
13.2 Claude Desktop and Claude Code
Claude Desktop uses a JSON mcpServers configuration. In the Developer settings, edit the Claude Desktop config file and add a server:
{
"mcpServers": {
"datalevin": {
"command": "dtlv",
"args": ["mcp"]
}
}
}
Claude Code can add the same local stdio server from the command line:
claude mcp add --transport stdio datalevin -- dtlv mcp
Restart the client, then ask it to inspect a local Datalevin database path or a remote dtlv:// database URI.
14. Troubleshooting and Cleanup
If the code examples do not run, check these common issues before moving deeper into the guide:
- Run
java -versionand confirm Java 21 or newer. - Make sure the Datalevin dependency version exists in your package repository. Chapter 2 uses the release line named in its dependency snippets.
- For Clojure and Java, make sure the JVM flags in Section 3 are actually passed to the process that starts Datalevin.
- Python and Node.js packages normally add the required JVM options for you.
- If you see
java.lang.UnsatisfiedLinkError, revisit the native dependency notes in Sections 2 and 4. - Do not repeatedly open the same local database path in the same process; use the connection lifecycle pattern from Chapter 22.
- If examples use
/tmp/datalevin-guide-getting-started,/tmp/mydb,/tmp/dtlv-cli,/tmp/bb-db, or$PWD/dtlv-data, remove those directories before rerunning from a clean slate. - For server examples, confirm the server process is still running, the port is
8898, and the password in the URI matchesDATALEVIN_DEFAULT_PASSWORD. - Keep MCP servers read-only until you deliberately need write tools.
Summary
Embedded mode is the easiest starting point, but the same database model carries across the server, CLI, Babashka, Docker, and MCP surfaces. Chapter 2 gives the minimal embedded path; Chapter 22 explains production lifecycle and operations.
User Examples
Log in to create examplesNo examples for this chapter yet.
