Licensing in 2026
CockroachDB Core moved to BSL in late 2024 (similar to HashiCorp's move). For internal use, BSL CockroachDB is free; for hosted-service redistribution, the license requires a paid agreement. The fork to watch is OpenSpyce / community-led; for most teams running CockroachDB internally, BSL is fine.
Install for a local 3-node cluster
CR_VER=v24.3.1
curl -L "https://binaries.cockroachdb.com/cockroach-${CR_VER}.linux-amd64.tgz" \
| tar -xz
sudo cp cockroach-${CR_VER}.linux-amd64/cockroach /usr/local/bin/
cockroach version
Three nodes, one machine (for testing)
mkdir -p ~/crdb/node{1,2,3}
# Generate CA + node certs for an insecure-development cluster, or use --insecure
# (only for local testing; never insecure mode on a network).
cockroach cert create-ca \
--certs-dir=~/crdb/certs --ca-key=~/crdb/ca.key
for n in 1 2 3; do
cockroach cert create-node localhost 127.0.0.1 \
--certs-dir=~/crdb/certs --ca-key=~/crdb/ca.key
done
cockroach cert create-client root --certs-dir=~/crdb/certs --ca-key=~/crdb/ca.key
# Start three nodes on different ports
cockroach start --insecure --store=node1 --listen-addr=127.0.0.1:26257 \
--http-addr=127.0.0.1:8080 --join=127.0.0.1:26257,127.0.0.1:26258,127.0.0.1:26259 \
--background
cockroach start --insecure --store=node2 --listen-addr=127.0.0.1:26258 \
--http-addr=127.0.0.1:8081 --join=127.0.0.1:26257,127.0.0.1:26258,127.0.0.1:26259 \
--background
cockroach start --insecure --store=node3 --listen-addr=127.0.0.1:26259 \
--http-addr=127.0.0.1:8082 --join=127.0.0.1:26257,127.0.0.1:26258,127.0.0.1:26259 \
--background
# Bootstrap the cluster (run once across the joined nodes)
cockroach init --insecure --host=127.0.0.1:26257
Three nodes running locally; admin UI at http://localhost:8080.
Production: 3+ nodes across availability zones
For real durability, run one node per AZ in a region (3-node minimum for fault tolerance, 5 nodes for tolerating 2 simultaneous failures). Each node:
cockroach start \
--certs-dir=/etc/cockroach/certs \
--store=path=/var/lib/cockroach,attrs=ssd \
--listen-addr=:26257 \
--http-addr=:8080 \
--advertise-addr=node1.lab.example.com:26257 \
--join=node1.lab.example.com:26257,node2.lab.example.com:26257,node3.lab.example.com:26257 \
--locality=region=us-east,zone=us-east-1a \
--cache=.25 --max-sql-memory=.25
--locality tags are critical — CockroachDB uses them to spread replicas across failure domains and to make region-aware routing decisions.
Connect with psql (or any Postgres client)
cockroach sql --insecure --host=localhost:26257
# Or with TLS:
cockroach sql --certs-dir=/etc/cockroach/certs --host=node1.lab.example.com:26257
# Or with stock psql against the Postgres-wire endpoint
psql -h node1.lab.example.com -p 26257 -U root postgres
From any application, treat it as Postgres:
DATABASE_URL=postgresql://root@node1.lab.example.com:26257/myapp?sslmode=verify-full&sslrootcert=/etc/cockroach/certs/ca.crt
Schema + transactions
CREATE DATABASE myapp;
USE myapp;
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email STRING NOT NULL UNIQUE,
name STRING,
created TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
title STRING NOT NULL,
body STRING,
INDEX (user_id, created)
);
-- Transactions work like Postgres
BEGIN;
INSERT INTO users (email, name) VALUES ('alice@example.com', 'Alice');
INSERT INTO posts (user_id, title, body) VALUES
((SELECT id FROM users WHERE email='alice@example.com'),
'First post', 'Hello world');
COMMIT;
CockroachDB's isolation defaults to SERIALIZABLE (Postgres defaults to READ COMMITTED). Stricter than what most apps actually need, but the guarantee is stronger; expect more retries on contended writes (Cockroach surfaces a 40001 serialization-failure error which the client retries).
The big differences from Postgres
- Schema changes are online and asynchronous.
ALTER TABLE ADD COLUMNdoesn't lock; the column is rolled out cluster-wide in the background. Status visible viaSHOW JOBS. - No materialized views. Use a regular VIEW + caching layer, or schedule a recurring INSERT...SELECT into a real table.
- Triggers are limited. Basic BEFORE INSERT / UPDATE triggers are supported in 24.x; complex Postgres trigger workloads need restructuring.
- Stored procedures & PL/pgSQL — gaining support, but historically a non-feature. Logic lives in the application.
- Per-row primary keys are 64-bit hash of the actual key — range queries on monotonic primary keys (e.g.
id SERIAL) create hotspots; use UUIDs or hash partitioning instead. Cockroach'sgen_random_uuid()default is the canonical choice. - EXPLAIN ANALYZE returns distributed-execution plans — takes practice to read vs Postgres's familiar single-node trees.
Multi-region survivability
The killer feature. With nodes spread across regions:
-- Pin a database's replicas across regions
ALTER DATABASE myapp PRIMARY REGION 'us-east-1';
ALTER DATABASE myapp ADD REGION 'us-west-2';
ALTER DATABASE myapp ADD REGION 'eu-central-1';
-- Per-table policies: where do replicas live?
ALTER TABLE users SET LOCALITY GLOBAL; -- replicas everywhere; high read availability
ALTER TABLE posts SET LOCALITY REGIONAL BY ROW; -- per-row pinning to a region
ALTER TABLE sessions SET LOCALITY REGIONAL BY TABLE IN 'us-east-1';
Lose a whole region: surviving regions still serve reads + writes for non-pinned tables; pinned tables lose availability for that region's data only. No "promote a replica" or "fail over"; the cluster keeps running.
Backups
-- Manual backup to S3
BACKUP DATABASE myapp INTO 's3://my-bucket/cockroach-backups?AWS_ACCESS_KEY_ID=...&AWS_SECRET_ACCESS_KEY=...';
-- Scheduled (Enterprise feature in BSL; OSS users use cron + above)
CREATE SCHEDULE my_backup_schedule
FOR BACKUP INTO 's3://my-bucket/cockroach-backups'
RECURRING '@daily'
WITH SCHEDULE OPTIONS first_run = 'now';
-- Restore
RESTORE DATABASE myapp FROM LATEST IN 's3://my-bucket/cockroach-backups';
When CockroachDB is the right tool
- Workloads that have outgrown one Postgres node and the team prefers continued SQL semantics over sharding.
- Multi-region active-active where eventual-consistency systems like DynamoDB Global Tables aren't an option due to transaction needs.
- Operational simplicity at scale — the cluster auto-rebalances, repairs failed nodes, handles version upgrades node-by-node without downtime.
When it's wrong
- For a single-node workload that fits comfortably on Postgres, CockroachDB's per-query overhead (network round-trips between SQL and KV layer) is noticeable; plain Postgres is faster.
- For analytics / OLAP, the row-oriented design is the wrong shape; use ClickHouse (see that tutorial) or DuckDB (see that tutorial).
- For apps that depend heavily on Postgres extensions (PostGIS, pgvector, hstore) — CockroachDB has spatial support and a vector extension as of 24.x, but the ecosystem is much narrower than Postgres's.
Alternatives in the same space
- TiDB — PingCAP's distributed SQL; MySQL-protocol-compatible instead of Postgres. Different SQL dialect; similar architectural pitch.
- YugabyteDB — another distributed SQL; Postgres-protocol-compatible; the YSQL layer reuses Postgres's parser/planner so dialect compatibility is closer.
- Neon / Aurora / AlloyDB — managed Postgres with separated compute/storage; horizontal-read scaling but single-writer.