Sign inSign up

sbansal793/thunderstrike

By sbansal793

•Updated 6 days ago

Key-value database server: TLS 1.3, encrypted at rest, io_uring. Proprietary, free to run.

Image
Databases & storage
0

538

sbansal793/thunderstrike repository overview

⁠thunderstrike

A key-value database server. TLS 1.3 only, every value encrypted at rest, and disk and network I/O through io_uring. Values from a few bytes to 512 MiB with a suitably sized arena.

thunderstrike is proprietary software of SHORTSQUEEZE PRIVATE LIMITED, free to download and run. See License below.

⁠Tags

TagPlatformsRuns on
0.1.3, latestlinux/amd64, linux/arm64x86-64-v3 (AVX2) with RDRAND; ARMv8.2 with LSE, AES and PMULL (Neoverse N1 target)
0.1.3-v4linux/amd64x86-64-v4 (AVX-512) with RDRAND; exits with SIGILL (status 132) if required CPU features are missing
0.1.0linux/amd64, linux/arm64Earlier release; same CPU requirements as the main 0.1.3 tag

latest points to 0.1.3. Pin 0.1.3 to select this release explicitly.

The main 0.1.3 tag holds both architectures; Docker pulls the one for your machine. The -v4 tag is AMD64 only. On macOS and Windows, Docker Desktop runs the server in its Linux VM. The instructions below describe 0.1.3.

⁠Quick start

You need Docker with Compose v2 and a Linux kernel of 6.8 or newer on the Docker host. Startup refuses older kernels. On macOS and Windows, upgrade Docker Desktop's Linux VM if needed; a newer container base image does not change its kernel. Health becomes ready after all reactors finish database recovery and their ports are listening.

Allow at least two physical cores visible to the container and 4 GiB for the default locked arenas, plus memory for the database, workers and OS. Docker Desktop's VM must have enough CPU and memory assigned to it. CPU topology inside that VM can differ from the host's topology.

Commands below use Bash (Linux, macOS or WSL on Windows), Docker Compose v2, and OpenSSL on the machine running the commands. Check the Docker host's kernel with docker info --format '{{.KernelVersion}}'.

1. Get the kit. The image writes its compose file, settings, seccomp profile, guide and license into an empty directory:

mkdir thunderstrike && cd thunderstrike
docker run --rm -v "$PWD:/out" docker.io/sbansal793/thunderstrike:0.1.3 kit /out

2. Settings. Copy the example:

cp .env.example .env

Edit .env before continuing. Set THUNDERSTRIKE_CORES to an even physical-core budget (at least 2) and THUNDERSTRIKE_TLS_PORT to one starting port. The kit already sets THUNDERSTRIKE_IMAGE to this release. Then generate the port mapping:

docker compose -f compose.yaml run --rm configure

configure detects CPU topology and writes compose.override.yaml with the derived reactor ports. Compose loads it automatically; no port range is entered. Use plain docker compose for subsequent commands so it loads the override.

3. The keystore passphrase. The server will not start without it, and losing it, or the data volume, loses every value. Keep a copy with your backups.

mkdir -p docker/secrets docker/tls
openssl rand -base64 32 > docker/secrets/passphrase
chmod 600 docker/secrets/passphrase

4. TLS. Clients trust a CA, and the server presents a certificate that CA issued. This makes both in docker/tls/: a CA (ca.crt for clients, ca.key to keep secret), and the server's certificate for the names in THUNDERSTRIKE_TLS_NAMES (default localhost). To renew the certificate, delete server.crt and server.key and run it again: the CA stays, so clients change nothing. An Ed25519 CA of your own works too; put it there as ca.crt and ca.key first.

docker compose run --rm tls-init

5. Start it, and wait for healthy:

docker compose up -d thunderstrike
docker compose ps

If it exits or remains unhealthy, read docker compose logs thunderstrike before continuing. Recovery time depends on the data and storage.

6. Check it. The sample client writes values of 256 B to 16 MiB on 8 connections for 30 seconds, reads every one back, compares them byte for byte, and exits 0 only with no error and no mismatch:

docker compose run --rm sample

The kit's README.md is the full guide: every setting, TLS, cores and ports, durability, large values, backups, upgrades and troubleshooting.

⁠The compose file

This follows the kit's compose.yaml, with the fallback image pinned to the published 0.1.3 tag. It needs the kit's docker/seccomp-io_uring.json beside it, and reads its settings from .env.

# thunderstrike under Docker Compose. README.md (the guide) explains every
# setting here.
#
# First time:
#   cp .env.example .env                                  # then edit it
#   docker compose -f compose.yaml run --rm configure       # derives reactor ports from CPU topology
#   mkdir -p docker/secrets && openssl rand -base64 32 > docker/secrets/passphrase
#   docker compose run --rm tls-init                      # a CA and the server's certificate in docker/tls/
#   docker compose up -d
#   docker compose run --rm sample                        # a client that writes, reads back, verifies
#
# Every path and size comes from the environment or .env (.env.example lists
# them all). Sizes are plain byte counts.
name: thunderstrike

services:
  thunderstrike:
    image: ${THUNDERSTRIKE_IMAGE:-docker.io/sbansal793/thunderstrike:0.1.3}
    environment:
      # Inside the container the secrets below are files under /run/secrets;
      # these name them. The host files they come from are set with the
      # THUNDERSTRIKE_HOST_* variables (bottom of this file).
      THUNDERSTRIKE_PASSPHRASE_FILE: /run/secrets/thunderstrike_passphrase
      THUNDERSTRIKE_TLS_CERT: /run/secrets/thunderstrike_tls_cert
      THUNDERSTRIKE_TLS_KEY: /run/secrets/thunderstrike_tls_key
      # Physical-core budget, an even integer at least 2. Actual CPU
      # topology determines reactor/helper pairs. Logical shards stay 1024.
      THUNDERSTRIKE_CORES: ${THUNDERSTRIKE_CORES:-2}
      # One starting port; the remaining reactor ports are derived.
      THUNDERSTRIKE_TLS_PORT: ${THUNDERSTRIKE_TLS_PORT:-7380}
      # configure writes the matching signature and ports into the
      # automatically loaded compose.override.yaml. A stale mapping refuses.
      THUNDERSTRIKE_PORT_LAYOUT: unconfigured
      # Per reactor, locked in memory. Must hold two copies of the largest value
      # one SET writes: 4294967296 (4 GiB) for 512 MiB values.
      # Empty: 4 GiB for one reactor, 2 GiB each for two or more.
      THUNDERSTRIKE_ARENA_BYTES: ${THUNDERSTRIKE_ARENA_BYTES:-}
      # Each shard's value file is fallocated to this at creation: the disk
      # taken up front is this x 1024 on a new database. Disabled by
      # default so the fixed logical layout does not reserve 512 GiB.
      THUNDERSTRIKE_BLOB_PREALLOC_BYTES: ${THUNDERSTRIKE_BLOB_PREALLOC_BYTES:-0}
    secrets:
      - thunderstrike_passphrase
      - thunderstrike_tls_cert
      - thunderstrike_tls_key
    # configure generates the exact host/container mapping from CORES,
    # CPU topology and TLS_PORT; users never calculate a range.
    ports: []
    volumes:
      # The database: shard files and the keystore. A named volume (default)
      # or a host directory: THUNDERSTRIKE_HOST_DATA=/srv/thunderstrike.
      # Back it up together with the passphrase.
      - ${THUNDERSTRIKE_HOST_DATA:-data}:/var/lib/thunderstrike
    ulimits:
      # The arena is locked in memory; the server refuses to start when the
      # limit cannot hold the arena bytes x the derived reactor count.
      memlock: -1
    security_opt:
      # The server does all its disk and network I/O through io_uring, which
      # Docker's default seccomp profile refuses. This profile is Docker's
      # default plus the three io_uring calls.
      - seccomp=docker/seccomp-io_uring.json
    # SIGTERM drains in-flight requests and checkpoints the index; do not let
    # Docker SIGKILL it after the default 10 s.
    stop_grace_period: 2m
    restart: unless-stopped

  # Derive the Docker mapping on the same Docker host as the server.
  # Run again after changing CORES, TLS_PORT or the available CPU topology.
  configure:
    profiles: ["init"]
    image: ${THUNDERSTRIKE_IMAGE:-docker.io/sbansal793/thunderstrike:0.1.3}
    environment:
      THUNDERSTRIKE_CORES: ${THUNDERSTRIKE_CORES:-2}
      THUNDERSTRIKE_TLS_PORT: ${THUNDERSTRIKE_TLS_PORT:-7380}
    volumes:
      - .:/out
    command: ["configure", "/out/compose.override.yaml"]

  # One-shot TLS setup in THUNDERSTRIKE_HOST_TLS_DIR. Clients trust a CA and
  # the server presents a certificate that CA issued. The first run makes the
  # CA: ca.crt, which clients trust, and ca.key, which signs and stays here.
  # Then it issues server.crt and server.key for THUNDERSTRIKE_TLS_NAMES
  # (separated by spaces or commas); clients connect by one of the names. It
  # does nothing while server.crt or server.key exists: delete both and run
  # it again to renew, and the same CA issues the new one. An Ed25519 CA of
  # your own works too: put it here as ca.crt and ca.key first.
  tls-init:
    profiles: ["init"]
    image: ${THUNDERSTRIKE_IMAGE:-docker.io/sbansal793/thunderstrike:0.1.3}
    environment:
      THUNDERSTRIKE_TLS_NAMES: ${THUNDERSTRIKE_TLS_NAMES:-localhost}
    volumes:
      - ${THUNDERSTRIKE_HOST_TLS_DIR:-./docker/tls}:/out
    command: ["tls-init", "/out"]

  # A sample client: thunderstrike-soak writes values of five sizes on 8
  # connections for 30 s, reads every one back and compares it byte for
  # byte. It exits 0 only with no mismatch and no error. It trusts ca.crt
  # and verifies the server's name as "localhost", so keep localhost in
  # THUNDERSTRIKE_TLS_NAMES to run it.
  sample:
    profiles: ["sample"]
    image: ${THUNDERSTRIKE_IMAGE:-docker.io/sbansal793/thunderstrike:0.1.3}
    depends_on:
      thunderstrike:
        condition: service_healthy
    volumes:
      - ${THUNDERSTRIKE_HOST_TLS_DIR:-./docker/tls}/ca.crt:/certs/ca.crt:ro
    command:
      - thunderstrike-soak
      - -host
      - thunderstrike
      - -tls-port
      - "${THUNDERSTRIKE_TLS_PORT:-7380}"
      # The shard count and the cores from the server's DISCOVER table.
      - -layout
      - server
      - -ca
      - /certs/ca.crt
      - -conns
      - "8"
      - -pipeline
      - "4"
      - -live
      - "40"
      - -sizes
      - "256:30,4k:30,64k:20,2m:15,16m:5"
      - -duration
      - 30s

volumes:
  data:

secrets:
  thunderstrike_passphrase:
    file: ${THUNDERSTRIKE_HOST_PASSPHRASE:-./docker/secrets/passphrase}
  thunderstrike_tls_cert:
    file: ${THUNDERSTRIKE_HOST_TLS_DIR:-./docker/tls}/server.crt
  thunderstrike_tls_key:
    file: ${THUNDERSTRIKE_HOST_TLS_DIR:-./docker/tls}/server.key

⁠Settings

Set them in .env. Sizes are plain byte counts (4294967296, not 4g). Compose passes the settings shown in its environment blocks; adding an arbitrary variable to .env does not pass it into the server container.

VariableDefault
THUNDERSTRIKE_IMAGEdocker.io/sbansal793/thunderstrike:0.1.3 in this kitImage used by all four services. Select 0.1.3-v4 only on a compatible AMD64 CPU.
THUNDERSTRIKE_CORES2Physical-core budget, even and from 2 through 1,024. Must fit the available CPU set/capacity. CPU topology determines reactor/helper pairs.
THUNDERSTRIKE_TLS_PORT7380One starting port. Configure generates the remaining reactor ports and Docker mapping.
THUNDERSTRIKE_ARENA_BYTESautomaticLocked memory per reactor: 4 GiB with one reactor, 2 GiB each otherwise. Use 4294967296 for 512 MiB values.
THUNDERSTRIKE_BLOB_PREALLOC_BYTES0Bytes reserved per logical shard at creation; disabled by default. A nonzero value is multiplied by 1,024.
THUNDERSTRIKE_TLS_NAMESlocalhostThe DNS names tls-init issues the certificate for. Clients connect by one of them.
THUNDERSTRIKE_HOST_DATAdataThe named volume thunderstrike_data, or a host path to bind-mount instead.
THUNDERSTRIKE_HOST_PASSPHRASE./docker/secrets/passphraseHost file containing the keystore passphrase.
THUNDERSTRIKE_HOST_TLS_DIR./docker/tlsHost directory containing the CA and server certificate/key files.

Logical shards are fixed internally at exactly 1,024. Users set the physical-core budget and one starting port. A stored count other than 1,024 is refused; migrate the data rather than editing its count record. There is no user shard-count setting and no port per logical shard.

Each reactor group uses two hardware threads (logical CPUs): one for the foreground reactor and one shared by background workers. On a CPU with two usable hardware threads per physical core, 8 physical cores make 8 reactors. With one hardware thread per core, they make 4 reactors. Actual SMT siblings are detected even when their CPU IDs are not adjacent. CPU affinity and quotas are respected; an allocation exceeding them refuses.

Physical cores (CORES)Usable hardware threads per coreDerived reactorsLogical shards per reactorGenerated ports from 7380
2111,0247380
2225127380-7381
8142567380-7383
8281287380-7387

For R derived reactors, each owns floor(1024 / R) logical shards; the first 1024 % R reactors get one extra. Mixed or restricted CPU sets use complete sibling pairs first and then pair remaining single hardware threads. An unpaired thread is left unused and reported. Reactor counts may be odd even though the physical-core budget is even.

To change the core budget or starting port, first stop the server cleanly:

docker compose stop thunderstrike

Edit .env, then run:

docker compose -f compose.yaml run --rm configure
docker compose up -d thunderstrike

Run configure again if the CPU topology or affinity changes. It uses the CPUs visible inside the Docker host/VM. Missing or stale generated ports refuse startup. After a crash, recover with an allocation producing the previous reactor count and stop cleanly before changing it.

⁠Durability

The default write-ahead journal makes each write durable before the server acknowledges it. Crash recovery replays that journal; an orderly stop drains in-flight work and checkpoints the database. This relies on the storage device honoring durability flushes. No durability setting is needed for the configuration shown here.

⁠Connecting clients

Official client libraries, all over the same compiled core: one address in, the client asks the server for its layout and connects to every reactor's port itself. Source, docs and releases: github.com/TheManhattanProject/thunderstrike-client⁠

LanguageInstall
Pythonpip install thunderstrike (PyPI⁠)
Node.jsnpm install @shortsqueeze/thunderstrike (npm⁠)
Gogo get github.com/TheManhattanProject/thunderstrike-client/[email protected]
Rustcargo add thunderstrike (crates.io⁠)
.NETdotnet add package Thunderstrike (NuGet⁠)
Javaio.github.maverick-ai:thunderstrike-client:0.1.3 (Maven Central⁠); JDK 22+
PHPcomposer require themanhattanproject/thunderstrike-client, then vendor/bin/thunderstrike-install (Packagist⁠); enable PHP FFI
C / C++header and libraries from the releases⁠

Client 0.1.3 packages cover Linux x86-64 and ARM64, Windows x86-64, and macOS on Intel and Apple Silicon. See the client README for each language's runtime requirements. Client versions are separate from server image tags.

Clients trust docker/tls/ca.crt and connect by a name the server's certificate carries (localhost unless you set THUNDERSTRIKE_TLS_NAMES). After the quick start above, from the same directory:

from thunderstrike import Thunderstrike

db = Thunderstrike("127.0.0.1:7380",
                   tls_server_name="localhost",
                   tls_ca_pem=open("docker/tls/ca.crt").read())
db.set(b"user:1", b"ada")
print(db.get(b"user:1"))   # b"ada", or None on a miss

Every language's version of this is in the client README⁠. A client connects to every reactor's port on the same host it was given, so each derived port must be reachable on that host, with the same port number inside and outside the container. Allow those ports through any firewall between the client and server.

The application supplies one bootstrap address. With the defaults, db:7380 is enough for the client to fetch DISCOVER: 1,024 logical shards and the derived reactor ports. With two reactors, shards 0-511 are on 7380 and 512-1023 on 7381. With one, all are on 7380. The client computes CRC32C(key) % 1024, finds that shard's entry in the returned table, and sends the request on a connection to its reactor's port. The application does not select ports for individual requests. Logical shards have no individual listening ports. The server persists the layout epoch and advances it when reactor ownership or ports change, so clients can identify a new routing map.

⁠Without Compose

First extract the kit, copy and edit .env, and create the passphrase as above. The command below creates the TLS identity. It uses the default host file paths; adjust the bind mounts if you changed them. The ports command and server must use the same core budget, starting port and CPU restrictions.

docker volume create thunderstrike_data
mkdir -p docker/tls
docker run --rm --env-file .env -v "$PWD/docker/tls:/out" \
  docker.io/sbansal793/thunderstrike:0.1.3 tls-init /out
published_ports=$(docker run --rm --env-file .env docker.io/sbansal793/thunderstrike:0.1.3 ports)
docker run -d --name thunderstrike \
  --env-file .env \
  --ulimit memlock=-1 \
  --security-opt seccomp=docker/seccomp-io_uring.json \
  --stop-timeout 120 \
  -p "$published_ports" \
  -v thunderstrike_data:/var/lib/thunderstrike \
  -v "$PWD/docker/secrets/passphrase:/run/secrets/passphrase:ro" \
  -v "$PWD/docker/tls/server.crt:/run/tls/server.crt:ro" \
  -v "$PWD/docker/tls/server.key:/run/tls/server.key:ro" \
  -e THUNDERSTRIKE_PASSPHRASE_FILE=/run/secrets/passphrase \
  -e THUNDERSTRIKE_TLS_CERT=/run/tls/server.crt \
  -e THUNDERSTRIKE_TLS_KEY=/run/tls/server.key \
  docker.io/sbansal793/thunderstrike:0.1.3

The seccomp profile is required: the server cannot run without io_uring, which Docker's default profile refuses. --ulimit memlock=-1 is required because the arena is locked in memory.

⁠Data and backups

The database lives in the volume at /var/lib/thunderstrike, keystore included. Stop the server before copying it. This command uses the stopped server container's actual mounts, so it works with a named volume or the THUNDERSTRIKE_HOST_DATA bind mount:

docker compose stop thunderstrike
docker run --rm \
  --volumes-from "$(docker compose ps -aq thunderstrike):ro" \
  -v "$PWD:/backup" debian:bookworm-slim \
  tar -C /var/lib/thunderstrike -czf /backup/thunderstrike-backup.tgz .
docker compose start thunderstrike

Keep the keystore passphrase securely with your backups; without it the archive cannot be decrypted. Back up the TLS CA, its private key and your configuration separately so clients can keep trusting the same CA.

⁠Troubleshooting

You seeWhat to do
io_uring probe failed: NotSupportedCheck the Docker host's kernel and use the kit's seccomp profile. Host security policy must also allow io_uring.
RLIMIT_MEMLOCK is 8192 KiB and the arena needs ...Keep ulimits: memlock: -1 in Compose, or use --ulimit memlock=-1. Ensure the Docker daemon's own limit permits this.
requested ... physical cores, but only ... availableLower THUNDERSTRIKE_CORES to an even value that fits the container, or allocate more CPUs to Docker. Run configure again.
Docker's published ports are missing or staleStop cleanly and rerun docker compose -f compose.yaml run --rm configure, then docker compose up -d thunderstrike.
A journal needs recovery with the previous reactor countRecover with the previous CPU allocation, stop cleanly, then change the layout.
The container is killed with exit status 137Check for an out-of-memory kill and increase Docker/host memory or reduce the configured arenas.
no keystore passphraseThe passphrase file is missing or empty.
keystore open failed: Kek(AuthFailed)The passphrase is wrong for this volume.
TLS is mandatoryRun docker compose run --rm tls-init.
Linux kernel 6.8 or newer is requiredUpgrade the host kernel, or Docker Desktop's Linux VM on macOS and Windows.
TLS cert ... is signed by itselfThe certificate is self-signed, as an older tls-init made them. Delete server.crt and server.key, run tls-init, and give clients ca.crt.
Exit status 132 (SIGILL)The -v4 tag on a CPU without AVX-512. Use the main tag.

⁠License

Copyright (c) 2026 SHORTSQUEEZE PRIVATE LIMITED. All rights reserved.

thunderstrike is proprietary software, licensed under the THUNDERSTRIKE END USER LICENSE AGREEMENT. You may download and run this image free of charge, for any lawful purpose, including in production and as the database behind your own applications and services. It is not open source. The agreement restricts modification, reverse engineering, resale, redistribution and offering the software as a hosted database service in its own right. It permits changing the kit's configuration for your own deployment; see the full agreement for its terms and exceptions. The software comes with no warranty, and the agreement is governed by the laws of India. By downloading or using the image you accept the agreement: read it in full in the kit's LICENSE, or print it:

docker run --rm docker.io/sbansal793/thunderstrike:0.1.3 license

The image includes third-party components under their own licenses. Their notices are in the image, in /usr/local/share/thunderstrike/third-party/ and /usr/share/doc/.

Tag summary

Content type

Image

Digest

sha256:248b7e0d1…

Size

11.5 MB

Last updated

6 days ago

docker pull sbansal793/thunderstrike