Self-hosted service
denoland/denokv avatar
denoland/denokv

denokv: running Deno KV on your own server

A self-hosted backend for Deno KV

652 stars38 forksTypeScriptMIT

At a glance

What is it?
denokv is a self-hosted backend for Deno KV that speaks the KV Connect protocol over plain HTTP and stores everything in a SQLite file. It is aimed at teams that need more than one Deno process sharing a key-value database and are willing to operate the server themselves.
Who is it for?
Adopt denokv when several Deno processes must share one KV database and you already have a host with persistent storage and a TLS terminating proxy. Do not adopt it for single-process development, for queue workloads, or on platforms that autoscale.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 28 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why denokv exists: one KV database behind several Deno processes

Deno KV normally comes in three shapes. The CLI carries a built-in single instance database, which is the easy choice for tests and local scripts. Deno Deploy offers a hosted and scalable backend. denokv is the third option, a self-hostable backend that runs on a machine you control.

The gap it fills is narrow and real. The single instance database lives inside one process, so two Deno processes cannot point at the same store and share state. If your application is split across a web process, a worker and a background job, the built-in store forces you to invent a different datastore for the shared parts. denokv gives those processes a single KV endpoint over the network instead.

The documentation is blunt about who it is for. You should use it if you need more than a single Deno process to reach the same KV database and you are willing to run a server, keep it updated, handle backups and do regular maintenance. Everything in the project design follows from that sentence: statefulness, a database file on disk, an access token, and a deployment model that expects one long-lived instance.

The SQLite file, the Rust workspace and the KV Connect wire

denokv is not a TypeScript server despite the repository language label. The workspace Cargo.toml lists five crates: denokv, proto, remote, sqlite and timemachine, each pinned to version 0.14.0, and the Dockerfile builds with cargo build --release on rust:1.83-bookworm before copying the release binary into a distroless Debian 12 image.

The storage engine is SQLite. The project notes that the self-hosted backend is built on the same SQLite backend as the built-in single instance database in the CLI, so operators inherit a familiar engine rather than a bespoke storage system. The dependency list shows rusqlite 0.40.0 with a fallible_uint feature, prost and prost-build for protocol buffers, and axum with http2 for the server side. tokio with the full feature set and tokio-util for streams support the async runtime, and the description of the binary says it uses non-blocking IO so that hundreds of concurrent connections do not block each other.

On the wire it speaks KV Connect, the protocol Deno Deploy also uses, which is why the same client code path covers both. The README claims the standalone binary is designed to handle thousands of concurrent requests from hundreds of different Deno processes. That is a design target stated by the project, not a measurement reported in the docs.

First run: the container, the token and the client URL

The shortest path to a running server is the published container invocation. It mounts a data directory, publishes port 4512 and passes an access token plus the path of the SQLite file:

sh
docker run -it --init -p 4512:4512 -v ./data:/data ghcr.io/denoland/denokv --sqlite-path /data/denokv.sqlite serve --access-token <random-token>

Before that, the setup instructions ask for a directory to hold the database:

sh
$ mkdir -p /data

The client then connects over plain HTTP with the token supplied through the DENO_KV_ACCESS_TOKEN environment variable:

ts
const kv = await Deno.openKv("http://localhost:4512");

You should expect the server to hold no state in memory beyond the open database and the token check. Everything that matters lives in the SQLite file at the path you passed, so the volume mount is the part of this command that matters most for durability. The access token is compared in constant time according to the dependency list, which is the right instinct for a value that gates the whole database, and the token is the only credential in the design.

denokv speaks only plain HTTP, so Caddy or nginx must terminate TLS

The server has no TLS of its own. The documentation says plainly that denokv only speaks plain HTTP and that it should be placed behind an HTTPS terminating proxy or load balancer, warning that running without TLS poses a significant security risk.

The worked example runs denokv and Caddy together, with the proxy in front of the denokv container:

yaml
services:
  denokv:
    image: ghcr.io/denoland/denokv
    command: --sqlite-path /data/denokv.sqlite serve --access-token ${DENO_KV_ACCESS_TOKEN}
    volumes:
      - ./data:/data
  caddy:
    image: caddy:2
    ports:
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy-data:/data
volumes:
  caddy-data:

The Caddyfile itself is short, and Caddy is the recommended option because it obtains and renews certificates on its own:

text
kv.example.com {
    reverse_proxy denokv:4512
}

Clients then use the public URL:

js
const kv = await Deno.openKv("https://kv.example.com/");

There is one nginx specific detail worth remembering: disable response buffering with proxy_buffering off;, because the watch endpoint streams its response and a buffering proxy will hold watch notifications back indefinitely. That single setting decides whether live key watching feels instant or appears broken.

Fly.io gets a volume, Google Cloud Run and Lambda do not qualify

The deployment guidance is shaped by a hard constraint: the service needs persistent storage and must not autoscale past a single instance. The README states directly that denokv cannot run on Google Cloud Run or AWS Lambda, since those platforms autoscale and have no persistent disk to hang the SQLite file on.

Fly.io works because a volume can be mounted. The documented fly.toml looks like this:

toml
app = "<your-app-name>"
primary_region = "<region>"

[build]
  image = "ghcr.io/denoland/denokv:latest"

[http_service]
  internal_port = 4512
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 0

[env]
  DENO_KV_SQLITE_PATH="/data/denokv.sqlite3"
  # access token is set via `flyctl secrets set`

[mounts]
  destination = "/data"
  source = "denokv_data"

The volume has to exist before the first deploy, created with flyctl volumes create denokv_data, and the access token is injected through flyctl secrets set rather than written into the file. Two details in this configuration carry risk. min_machines_running = 0 lets Fly.io stop the machine, so a cold start sits in front of your first request. And auto_stop_machines pairs with auto_start_machines, meaning scale to zero is the default posture rather than an exception.

Queues are the one feature KV Connect cannot carry

The supported features table is the clearest statement of the trade-off in the project. get, getMany, set, delete and list work both locally and over KV Connect. atomic() with checks and the sum, min and max operations work over both. Key expiration through expireIn works over both. watch works over both.

Then there is enqueue and listenQueue, marked as unsupported for KV Connect and for Deno Deploy, with the reason given in parentheses: the protocol does not include queue operations. Queue calls work only against a local database, meaning a path passed to Deno.openKv, or the sqlite and in-memory backends of the @deno/kv npm package.

This is a protocol boundary rather than a bug in the server. The consequence is architectural: if your workload depends on Deno KV queues, denokv cannot serve that workload even though it satisfies the rest of the API. A team that migrates a queue based design to denokv needs a different transport for those messages, and the docs give no guidance on migrating them.

The cases where denokv is the wrong tool

Two situations clearly point elsewhere. For local development and testing, the CLI store is simpler and needs no server, no token and no volume. The built-in backend can be opened as an in memory database with Deno.openKv(":memory:"), or as a persistent database by giving Deno.openKv a path such as Deno.openKv("./my-database.sqlite"). The second case is Deno Deploy, which the docs offer when you do not want to host and maintain a denokv server yourself.

A third case is any platform without persistent storage or with autoscaling, as described above. A fourth is a workload built on queues. In all of these, denokv adds an operational burden without buying anything back, and the project's own guidance is explicit that running a server, updating it and handling backups is part of the deal rather than an exception.

One gap in the documentation is worth flagging for an evaluator: while the project asks operators to handle backups, the README does not document a backup procedure or a restore procedure for the SQLite file. You will be writing that runbook yourself.

MIT licensing and an uneven release history

The licence situation is simple. The workspace Cargo.toml marks the crate as MIT, and the README states that denokv is MIT licensed, free and open source, the same terms as the Deno CLI. The Dockerfile also carries an MIT label for org.opencontainers.image.licenses.

The release history is less smooth. The listed versions are 0.14.0 dated 2026-06-06, 0.7.0 dated 2023-12-21 and 0.6.1 dated 2023-12-08. The last push to the repository was on 2026-09-24, so the codebase is not dormant even though the two older tags sit more than two years apart from the newest one.

The upgrade path for operators is the container image and the pinned crate versions in Cargo.toml. The workspace keeps denokv_proto, denokv_sqlite, denokv_remote and denokv_timemachine at matching versions, so the crates move together, and the published image tag ghcr.io/denoland/denokv:latest is what the Fly.io configuration pulls. A container deployment makes a version bump a pull and restart, but because the schema and file format live in the SQLite file itself, you should test an upgrade against a copy of that file before it touches the live one.

Editorial conclusion

Adopt denokv when several Deno processes must share one KV database and you already have a host with persistent storage and a TLS terminating proxy. Do not adopt it for single-process development, for queue workloads, or on platforms that autoscale. Before shipping, verify that your host keeps the SQLite file on a persistent volume, that a TLS proxy sits in front of port 4512, and that your backups cover the file the --sqlite-path flag points at.

Frequently asked questions

How do I connect a Deno program to a self-hosted denokv server?

Start the server with a --sqlite-path and an --access-token, then set the DENO_KV_ACCESS_TOKEN environment variable and open the database with Deno.openKv using the host and port of the server, for example Deno.openKv("http://localhost:4512"). Put an HTTPS terminating proxy in front of it for anything beyond local use.

Does denokv support Deno KV queues such as enqueue and listenQueue?

No. The KV Connect protocol does not include queue operations, so enqueue and listenQueue work only against a local database such as a path passed to Deno.openKv or the sqlite and in-memory backends of the @deno/kv npm package. All other listed operations, including atomic(), expireIn and watch, work over KV Connect.

Can I run denokv on Google Cloud Run or AWS Lambda?

No. The project states that denokv needs a service with persistent storage that does not autoscale beyond a single instance, which rules out Google Cloud Run and AWS Lambda. A VPS, a Kubernetes cluster or a Fly.io app with a mounted volume fits the requirement.

What database does denokv store data in?

It uses SQLite, described as the same backend as the built-in single instance database in the Deno CLI. The path is chosen by the --sqlite-path flag, for example /data/denokv.sqlite, and that file is what you need to back up.

Official sources

  1. denoland/denokv on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/denoland-denokv.svg)](https://hysenlabs.com/projects/denoland-denokv)

Was this useful?