Skip to content
Docs

Getting Started

T4 has two modes. Pick the one that fits your use case:

Standalone serverGo library
Use caseDrop-in etcd replacement, Kubernetes metadata store, any languageGo apps that want zero-overhead local reads, no sidecar
ClientAny etcd v3 client (etcdctl, Go, Python, Java, …)Direct Go API calls
InstallBinary or Docker imagego get

Terminal window
go install github.com/t4db/t4/cmd/t4@latest

Or use Docker:

Terminal window
docker pull ghcr.io/t4db/t4:latest

Good for development or when durability comes from infrastructure (PVC, RAID):

Terminal window
t4 run --data-dir /var/lib/t4 --listen 0.0.0.0:3379

WAL segments and checkpoints are uploaded to S3. The node recovers automatically if it loses its disk:

Terminal window
t4 run \
--data-dir /var/lib/t4 \
--listen 0.0.0.0:3379 \
--s3-bucket my-bucket \
--s3-prefix t4/

AWS credentials are resolved from T4_S3_ACCESS_KEY_ID, T4_S3_SECRET_ACCESS_KEY environment variables.

Terminal window
etcdctl --endpoints=localhost:3379 put /config/timeout 30s
etcdctl --endpoints=localhost:3379 get /config/timeout
etcdctl --endpoints=localhost:3379 watch /config/ --prefix

The etcd Go client, Python etcd3, Java client, and etcdctl all work unchanged.

Use t4 inspect when you want to explore a local data directory without starting the server:

Terminal window
t4 inspect meta --data-dir /var/lib/t4
t4 inspect list --data-dir /var/lib/t4 --prefix /config/
t4 inspect get --data-dir /var/lib/t4 /config/timeout
t4 inspect history --data-dir /var/lib/t4 /config/timeout
t4 inspect diff --data-dir /var/lib/t4 --from-rev 10 --to-rev 20 --prefix /config/

All nodes run the same command — no membership config, no initial cluster flag. The first node to write the S3 lock becomes leader; the rest become followers.

Terminal window
# Node A
t4 run \
--data-dir /var/lib/t4 \
--listen 0.0.0.0:3379 \
--s3-bucket my-bucket \
--s3-prefix t4/ \
--node-id node-a \
--peer-listen 0.0.0.0:3380 \
--advertise-peer node-a.internal:3380
# Node B (same bucket, same prefix)
t4 run \
--data-dir /var/lib/t4 \
--listen 0.0.0.0:3379 \
--s3-bucket my-bucket \
--s3-prefix t4/ \
--node-id node-b \
--peer-listen 0.0.0.0:3380 \
--advertise-peer node-b.internal:3380

New nodes can join at any time — they restore the latest S3 checkpoint and start replicating. No configuration changes needed on existing nodes.

Terminal window
t4 run \
--data-dir /var/lib/t4 \
--listen 0.0.0.0:3379 \
--s3-bucket my-bucket \
--s3-prefix t4/ \
--s3-endpoint http://minio:9000

See the Kubernetes deployment guide for the Helm chart, which handles multi-node clustering, PVCs, TLS, IRSA, and Prometheus out of the box.


Use this when you want the store running inside your binary — no network hop for reads, no process to manage.

Terminal window
go get github.com/t4db/t4

Requires Go 1.22+.

import "github.com/t4db/t4"
node, err := t4.Open(t4.Config{
DataDir: "/var/lib/myapp/t4",
})
if err != nil {
log.Fatal(err)
}
defer node.Close()
// Write
rev, err := node.Put(ctx, "/config/timeout", []byte("30s"), 0)
// Read (no network, served from local Pebble)
kv, err := node.Get("/config/timeout")
fmt.Println(string(kv.Value)) // 30s
// List all keys under a prefix
kvs, err := node.List("/config/")
// Watch for changes
events, _ := node.Watch(ctx, "/config/", 0)
for e := range events {
fmt.Printf("%s %s=%s\n", e.Type, e.KV.Key, e.KV.Value)
}
import (
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/t4db/t4/pkg/object"
)
awsCfg, _ := awsconfig.LoadDefaultConfig(ctx)
store := object.NewS3Store(s3.NewFromConfig(awsCfg), "my-bucket", "t4/")
node, err := t4.Open(t4.Config{
DataDir: "/var/lib/myapp/t4",
ObjectStore: store,
})
import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/t4db/t4/pkg/object"
)
cfg, _ := config.LoadDefaultConfig(ctx,
config.WithRegion("us-east-1"),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("user", "pass", "")),
config.WithEndpointResolverWithOptions(
aws.EndpointResolverWithOptionsFunc(func(service, region string, opts ...any) (aws.Endpoint, error) {
return aws.Endpoint{URL: "http://minio:9000", HostnameImmutable: true}, nil
}),
),
)
store := object.NewS3Store(s3.NewFromConfig(cfg), "my-bucket", "t4/")