Three services replacing the legacy zcashd: Zebra (full node), Zaino (indexer), and Zallet (wallet). Orchestrated via Docker Compose with sensible defaults that work out of the box.
git clone https://github.com/ZcashFoundation/z3 && cd z3
# Generate required credentials
openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt \
-sha256 -days 365 -nodes -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1"
rage-keygen -o config/zallet_identity.txt
# Start Zebra first — it must sync before other services can start
# Mainnet: 24-72 hours | Testnet: 2-12 hours
docker compose up -d zebra
# Monitor sync (returns "ok" when ready)
curl http://localhost:8080/ready
# Once Zebra is synced, start the full stack
docker compose up -dPre-built images for all 3 services are pulled automatically. No build step or submodule init needed.
Warning
The TLS certificate and identity file must exist before running any docker compose command. If config/tls/zaino.crt or config/tls/zaino.key are missing, Compose will fail with a file-not-found error.
Important
Zebra must sync the blockchain before Zaino and Zallet can start. Running docker compose up -d on a fresh install without syncing Zebra first will cause the other services to fail repeatedly. Start Zebra alone, wait for sync, then start the rest.
Already have synced Zebra data? Start everything immediately:
docker compose up -d
docker compose ps # verify all healthyTip
Apple Silicon users: Create .env with DOCKER_PLATFORM=linux/arm64 for native builds.
The stack supports 3 network modes. Mainnet runs with zero configuration; testnet and regtest require a few overrides.
| Mode | Command | Use case |
|---|---|---|
| Mainnet | docker compose up -d |
Production, syncs the live Zcash blockchain |
| Testnet | Create .env with NETWORK_NAME=Testnet |
Testing against the public test network |
| Regtest | docker compose --env-file .env.regtest up -d |
Local development, instant blocks, no sync wait |
Create a .env file with the variables that differ from mainnet:
echo "NETWORK_NAME=Testnet" > .envUpdate config/zallet.toml to set network = "test" in the [consensus] section, then start the stack normally.
Regtest uses a compose overlay (docker-compose.regtest.yml) that adds the rpc-router service, disables TLS, and adjusts healthchecks for a peerless network. Volumes are automatically isolated via COMPOSE_PROJECT_NAME=z3-regtest.
First-time setup (required before starting the stack):
# Initialize wallet, generate RPC auth, and mine the first block
./scripts/regtest-init.sh
# Start the full regtest stack
docker compose --env-file .env.regtest up -dThe init script generates the Zallet RPC password hash, starts Zebra, mines the first block, and initializes the Zallet wallet. It is safe to re-run — it skips steps that are already done. Subsequent runs only need the last command.
See docs/regtest.md for test commands (curl, grpcurl) and the full workflow reference.
Prometheus, Grafana, Jaeger, and AlertManager are available behind a Docker Compose profile. Zebra metrics must be explicitly enabled for Prometheus to have data to scrape.
Add to your .env (or .env.regtest for regtest):
ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999Then start with the monitoring profile:
# Mainnet/Testnet
docker compose --profile monitoring up -d
# Regtest
docker compose --env-file .env.regtest --profile monitoring up -dAccess Grafana at http://localhost:3000 (admin/admin), Prometheus at http://localhost:9094, and Jaeger at http://localhost:16686.
graph LR
subgraph Z3["Z3 Stack"]
Zebra["Zebra<br/>(full node)"]
Zaino["Zaino<br/>(indexer)"]
subgraph Zallet["Zallet (wallet)"]
EmbeddedZaino["Embedded<br/>Zaino libs"]
end
end
Zebra -->|JSON-RPC :18232| Zaino
Zebra -->|JSON-RPC :18232| EmbeddedZaino
Zaino -->|gRPC :8137| LightClients["Light wallet<br/>clients"]
Zebra syncs and validates the Zcash blockchain. Zaino provides a lightwalletd-compatible gRPC interface for light wallet clients like Zingo. Zallet embeds Zaino's indexer libraries internally and connects directly to Zebra's JSON-RPC; it does not use the standalone Zaino service.
All images can be overridden via environment variables (ZEBRA_IMAGE, ZAINO_IMAGE, ZALLET_IMAGE). See .env.example for all available options.
| Service | Default Image | Source |
|---|---|---|
| Zebra | zfnd/zebra:4.2.0 |
ZcashFoundation/zebra |
| Zaino | ghcr.io/zcashfoundation/zaino:sha-0164cab |
zingolabs/zaino |
| Zallet | electriccoinco/zallet:v0.1.0-alpha.3 |
zcash/wallet |
- Docker Engine with Docker Compose (v2.24.0+)
- rage for generating Zallet encryption keys
- Git for cloning the repository
Note
Linux users may need sudo for Docker commands, or add your user to the docker group. See Docker's post-installation steps.
Once running, services are available at:
| Service | Endpoint | Default Port |
|---|---|---|
| Zebra RPC | http://localhost:18232 |
Z3_ZEBRA_HOST_RPC_PORT |
| Zebra Health | http://localhost:8080/ready |
Z3_ZEBRA_HOST_HEALTH_PORT |
| Zaino gRPC | localhost:8137 |
ZAINO_HOST_GRPC_PORT |
| Zaino JSON-RPC | http://localhost:8237 |
ZAINO_HOST_JSONRPC_PORT |
| Zallet RPC | http://localhost:28232 |
ZALLET_HOST_RPC_PORT |
docker compose down # stop containers, keep data
docker compose down -v # stop and delete all volumes (full reset)The sections below cover setup details, configuration, and operational topics. Expand the section you need.
System Requirements
- CPU: 2 cores (4+ recommended)
- RAM: 4 GB for Zebra alone; 8+ GB for the full stack
- Disk: Mainnet ~300 GB, Testnet ~30 GB (SSD strongly recommended)
- Network: Reliable internet; initial mainnet sync downloads ~300 GB
- CPU: 4+ cores
- RAM: 16+ GB
- Disk: 500+ GB with room for blockchain growth
- Network: 100+ Mbps, ~300 GB/month bandwidth
| Network | First sync | With existing data |
|---|---|---|
| Mainnet | 24-72 hours | Minutes |
| Testnet | 2-12 hours | Minutes |
Based on Zebra's official requirements. Zaino adds additional resource overhead; specific requirements are under determination.
Setup Details
Pre-built images are used by default. To build from source instead:
git submodule update --init --recursive
docker compose buildZaino's gRPC endpoint uses TLS. Generate a self-signed certificate:
openssl req -x509 -newkey rsa:4096 \
-keyout config/tls/zaino.key -out config/tls/zaino.crt \
-sha256 -days 365 -nodes -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1"For production, use certificates from a trusted CA.
rage-keygen -o config/zallet_identity.txtBack up this file and the public key printed to the terminal.
Review config/zallet.toml and set the network in the [consensus] section:
- Mainnet:
network = "main" - Testnet:
network = "test"
Zallet embeds Zaino's indexer libraries and connects directly to Zebra's JSON-RPC endpoint.
Critical requirements for config/zallet.toml:
validator_addressmust point tozebra:18232(Zebra's JSON-RPC), notzaino:8137- All TOML sections must be present:
[builder],[consensus],[database],[external],[features],[indexer],[keystore],[note_management],[rpc] - Cookie authentication must be configured in both TOML and mounted as a volume
Z3 defaults to AMD64 for consistency. On Apple Silicon or ARM64 Linux, emulation makes builds ~15x slower. Enable native builds:
echo "DOCKER_PLATFORM=linux/arm64" >> .envConfiguration Reference
Every variable in docker-compose.yml has a default via ${VAR:-default}. The stack works with zero configuration files. Create .env only to override specific values.
Precedence (highest wins):
- Shell environment variables
.envfile values- Compose file defaults
Variables follow a 3-tier naming system to avoid collisions:
| Prefix | Scope | Example |
|---|---|---|
Z3_* |
Infrastructure (volumes, ports); never passed to containers | Z3_ZEBRA_DATA_PATH |
| Unprefixed | Shared config, remapped per service in compose | NETWORK_NAME, ENABLE_COOKIE_AUTH |
ZEBRA_*, ZAINO_*, ZALLET_* |
Service-specific application config | ZEBRA_TRACING__FILTER |
# Network
NETWORK_NAME=Testnet
# Log levels
Z3_ZEBRA_RUST_LOG=debug
ZAINO_RUST_LOG=debug
# Ports
Z3_ZEBRA_HOST_RPC_PORT=28232
ZAINO_HOST_GRPC_PORT=9137
# Images
ZEBRA_IMAGE=zfnd/zebra:5.0.0See .env.example for all available variables.
Data Storage and Volumes
The stack uses Docker-managed named volumes by default:
| Volume | Contents |
|---|---|
zebra_data |
Blockchain state (~300 GB mainnet, ~30 GB testnet) |
zaino_data |
Indexer database |
zallet_data |
Wallet database |
shared_cookie_volume |
RPC authentication cookies |
For backups, external SSDs, or shared storage, override volume paths in .env:
Z3_ZEBRA_DATA_PATH=/mnt/ssd/zebra-state
Z3_ZAINO_DATA_PATH=/mnt/ssd/zaino-data
Z3_ZALLET_DATA_PATH=/mnt/ssd/zallet-dataFix permissions before starting:
./scripts/fix-permissions.sh zebra /mnt/ssd/zebra-state
./scripts/fix-permissions.sh zaino /mnt/ssd/zaino-data
./scripts/fix-permissions.sh zallet /mnt/ssd/zallet-dataEach service runs as a specific non-root user. Directories must have correct ownership (set by the script) and 700 permissions. Never use 755 or 777.
Health Checks and Sync Strategy
Zebra's blockchain sync takes hours to days. Docker Compose healthcheck timeouts cannot accommodate this, so the stack uses a two-phase approach:
- Start Zebra alone (
docker compose up -d zebra) - Wait for sync (
curl http://localhost:8080/readyreturns "ok") - Start the full stack (
docker compose up -d)
Zebra exposes 2 endpoints on port 8080:
| Endpoint | Returns 200 when | Use for |
|---|---|---|
/healthy |
Has minimum peer connections | Liveness monitoring, restart decisions |
/ready |
Synced within 2 blocks of tip | Production readiness, dependency gating |
Zebra (/ready — synced near tip)
→ Zaino (gRPC port responding)
→ Zallet (RPC responding)
The default compose configuration gates Zaino and Zallet on Zebra's /ready endpoint. For development, copy docker-compose.override.yml.example to docker-compose.override.yml to switch to /healthy (allows services to start during sync, but they may error until Zebra catches up).
| Mode | Zebra healthcheck | Behavior |
|---|---|---|
| Production (default) | /ready |
Two-phase: sync Zebra first, then start stack |
| Development (override) | /healthy |
Start immediately; services may error during sync |
curl http://localhost:8080/ready # "ok" when synced
docker compose logs -f zebra # watch logs
./scripts/check-zebra-readiness.sh # polls until synced, prints status every 30sWhat to expect during sync:
- Zebra shows
healthy (starting)while syncing (during the 90-second grace period) - Once synced,
/readyreturnsokand Zebra showshealthy - Zaino and Zallet remain in
waitingstate until Zebra is ready
# Adjust how many blocks behind the tip is acceptable (default: 2)
ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND=2
# Minimum peer connections for /healthy (default: 1, set 0 for regtest)
ZEBRA_HEALTH__MIN_CONNECTED_PEERS=1
# Make /ready always return 200 on testnet even during sync (default: false)
ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=false- docs/docker-architecture.md: Design decisions, Compose patterns, and rationale behind the stack's configuration
- docs/regtest.md: Regtest environment setup, test commands (curl, grpcurl), and workflow reference
- .env.example: All available environment variable overrides