Skip to main content

Quick Start

Get ScramDB running in 2 minutes with Docker, or natively on Linux with the one-line installer, then grow it into a three-node cluster.

1. Pull and Run​

docker pull scramdb/scramdb:latest

docker run -d \
--name scramdb \
-p 5432:5432 \
-p 9090:9090 \
-p 9191:9191 \
-v scramdb-data:/var/lib/scramdb \
scramdb/scramdb:latest

The three ports: 5432 is the PostgreSQL wire protocol, 9090 serves Prometheus metrics, and 9191 is the Semantic AI MCP endpoint for AI agents (see Semantic AI).

The -v scramdb-data:/var/lib/scramdb mount keeps your data across docker stop / docker start and even a docker rm of the container, since it lives in a named Docker volume instead of the container's writable layer.

No authentication by default

The published image's default startup command runs with authentication disabled (--pg-no-auth), listening on all interfaces. Anyone who can reach port 5432 can connect with any username, no password. This is intentional: it makes local development frictionless. It is not safe to expose this container to an untrusted network as-is.

To require passwords, override the container's default command and drop --pg-no-auth:

docker run -d \
--name scramdb \
-p 5432:5432 \
-p 9090:9090 \
-p 9191:9191 \
-v scramdb-data:/var/lib/scramdb \
scramdb/scramdb:latest \
--pg-address 0.0.0.0:5432

With authentication enabled, connections from localhost are still trusted without a password; connections from anywhere else must authenticate with SCRAM-SHA-256. The bootstrap scramdb role has no password set until you give it one:

ALTER ROLE scramdb WITH PASSWORD 'a-real-password';

Alternative: local install without Docker (Linux)​

Prefer a native binary? The installer downloads the latest build, verifies its checksum, installs everything into ~/.scramdb (binary, config, and the curated UDF packages), and puts scramdb on your PATH. It never touches an existing data directory or config.

curl -fsSL https://scramdb.com/install | bash

Then open a new terminal and start the server:

scramdb

That's it: it finds its config automatically, listens on 127.0.0.1:5432, and runs in the background (log: ~/.scramdb/scramdb.log). Manage it with scramdb status and scramdb stop. Connections from localhost are trusted without a password, so step 2 below works as-is.

Requirements: Linux x86-64 with AVX2 (Intel Haswell 2013 / AMD Zen 1 2017 or newer) and glibc 2.36+, or Linux arm64 (Neoverse-N1 baseline, e.g. AWS Graviton2+). The installer checks these up front and refuses with a clear message instead of installing a binary that would crash. On macOS there is no native build yet: use the Docker image above.

Useful variants:

curl -fsSL https://scramdb.com/install | bash -s -- --pick # choose a build/version
curl -fsSL https://scramdb.com/install | bash -s -- --no-modify-path # don't touch shell PATH config
curl -fsSL https://scramdb.com/install | bash -s -- --uninstall # remove everything, data included
curl -fsSL https://scramdb.com/install | bash -s -- --help # all options

2. Connect​

Use psql or any PostgreSQL client:

psql -h localhost -p 5432 -U scramdb scramdb

You should see:

scramdb=#

3. Create a Table​

CREATE TABLE sensors (
id BIGINT PRIMARY KEY,
device_id INTEGER NOT NULL,
temperature DOUBLE PRECISION,
humidity DOUBLE PRECISION,
recorded_at TIMESTAMP
);

4. Insert Data​

INSERT INTO sensors VALUES
(1, 101, 22.5, 45.0, '2024-03-15 10:00:00'),
(2, 101, 23.1, 44.0, '2024-03-15 10:05:00'),
(3, 102, 19.8, 55.0, '2024-03-15 10:00:00'),
(4, 102, 20.1, 54.0, '2024-03-15 10:05:00'),
(5, 103, 25.3, 38.0, '2024-03-15 10:00:00');

5. Query​

-- Basic aggregation
SELECT
device_id,
AVG(temperature) AS avg_temp,
MIN(humidity) AS min_humidity,
COUNT(*) AS readings
FROM sensors
GROUP BY device_id
ORDER BY avg_temp DESC;

-- Window function
SELECT
device_id,
recorded_at,
temperature,
temperature - LAG(temperature) OVER (
PARTITION BY device_id ORDER BY recorded_at
) AS temp_change
FROM sensors;

6. Load CSV Data​

COPY ... FROM '/path' reads a file on the server, inside the container, not a file on your machine. Unless you have mounted a matching volume, that path will not exist and the command will fail. For a file that lives on your own machine, use psql's \copy meta-command instead. It streams the file from the client to ScramDB, so no container-side path is needed.

First, create a CSV file on your machine:

cat > sensors.csv <<'EOF'
id,device_id,temperature,humidity,recorded_at
6,101,22.0,46.0,2024-03-15 10:10:00
7,103,24.9,37.5,2024-03-15 10:10:00
EOF

Then, from inside psql, load it:

scramdb=# \copy sensors FROM 'sensors.csv' (FORMAT CSV, HEADER)

If you do mount a volume and want the server to read the file itself, COPY works the same way psql clients expect:

-- Only works if /data/sensors.csv exists inside the container
-- (e.g. add -v $(pwd)/data:/data to your docker run command)
COPY sensors FROM '/data/sensors.csv' (FORMAT CSV, HEADER);

7. A Three-Node Cluster​

The same image runs a cluster. Three environment variables per container turn cluster mode on: NODE_NAME names the node, ADVERTISE_ADDR is the address its peers dial, and SEEDS lists its peers. Clustering needs an Enterprise or Trial license key (see Cluster licensing for how to get one); without it each node logs why and exits.

Stop the single node from step 1 first, so port 5432 is free:

docker stop scramdb

Start three nodes on one Docker network. They reach each other by container name on ports 7190, 7191 and 7192 inside the network, so only the client port is published:

export SCRAMDB_LICENSE_KEY="<your Enterprise or Trial key>"
docker network create scramdb-net

docker run -d --name node-a --hostname node-a --network scramdb-net \
-e NODE_NAME=node-a -e ADVERTISE_ADDR=node-a:7190 \
-e SEEDS='["node-b:7190", "node-c:7190"]' \
-e SCRAMDB_LICENSE_KEY \
-p 5432:5432 -v node-a-data:/var/lib/scramdb \
--stop-timeout 120 scramdb/scramdb:latest

docker run -d --name node-b --hostname node-b --network scramdb-net \
-e NODE_NAME=node-b -e ADVERTISE_ADDR=node-b:7190 \
-e SEEDS='["node-a:7190", "node-c:7190"]' \
-e SCRAMDB_LICENSE_KEY \
-p 5433:5432 -v node-b-data:/var/lib/scramdb \
--stop-timeout 120 scramdb/scramdb:latest

docker run -d --name node-c --hostname node-c --network scramdb-net \
-e NODE_NAME=node-c -e ADVERTISE_ADDR=node-c:7190 \
-e SEEDS='["node-a:7190", "node-b:7190"]' \
-e SCRAMDB_LICENSE_KEY \
-p 5434:5432 -v node-c-data:/var/lib/scramdb \
--stop-timeout 120 scramdb/scramdb:latest

-e SCRAMDB_LICENSE_KEY passes the key you exported into each container. --stop-timeout 120 gives a node the time it needs on docker stop to hand its data and its vote back to the others.

A node accepts clients once the cluster has formed, which it waits up to a minute for, so start all three together. Watch them turn healthy:

docker ps --filter name=node- --format "{{.Names}}: {{.Status}}"

Connect to any node and ask the cluster about itself:

psql -h localhost -p 5432 -U scramdb scramdb
SELECT node_id, role, state FROM scram.nodes ORDER BY node_id;
node_id | role | state
---------+-------+-------
node-a | voter | live
node-b | voter | live
node-c | voter | live
(3 rows)

Create a distributed table. On a cluster, every table with a primary key is distributed with no extra syntax: its rows are placed in buckets by a hash of the key, and each bucket is kept on three nodes (the replication factor), so the cluster keeps serving with one node down.

CREATE TABLE orders (
order_id BIGINT PRIMARY KEY,
customer_id BIGINT NOT NULL,
total NUMERIC(12, 2) NOT NULL
);

INSERT INTO orders
SELECT t.g, t.g % 100, t.g * 1.25
FROM generate_series(1, 10000) AS t(g);

SELECT count(*), sum(total) FROM orders;
count | sum
-------+-------------
10000 | 62506250.00
(1 row)

Every node serves the same table. Ask node-b, and see where the buckets live:

psql -h localhost -p 5433 -U scramdb scramdb -c "SELECT count(*) FROM orders"
psql -h localhost -p 5433 -U scramdb scramdb -c "SHOW SHARDS"

Remove the cluster and bring the single node back:

docker rm -f node-a node-b node-c
docker volume rm node-a-data node-b-data node-c-data
docker network rm scramdb-net
docker start scramdb

The engine repository's docker-compose.yml runs the same three nodes with one command. Next: Forming a cluster for Compose, bare metal and Kubernetes, Distributed Cluster for what a cluster guarantees and how to deploy it, and Ports for every port a node opens.

What's Next?​