Solana Docker local testnet on macOS: Validators isolated, gossip not working, only see themselves
Solana Docker local testnet on macOS: Validators isolated, gossip not working, only see themselves
Loading saved threads...
kangrangx · External communityPost link
External question — Solana Stack Exchange
Author: kangrangx
Original post: https://solana.stackexchange.com/questions/23070
License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/
Adaptation: HTML converted to plain text; contact email addresses removed.
I’m a small developer who recently got interested in Solana and started trying things out 😊
Right now, I’m attempting to build a local Solana testnet on macOS using Docker Desktop, just for learning and experimentation.
I’m running a local Solana testnet on macOS using Docker Desktop. I’m using Docker Compose to launch 5 validators (validator1 through validator5), each in its own container. validator1 generates the genesis ledger and shares it with the others via a volume mount.
The setup uses a custom Docker bridge network with static IPs like 172.16.1.x, and each validator is configured with:
--identity, --vote-account, --ledger (all unique per validator)
--gossip-host, --gossip-port, and --entrypoint validator1:8003 (for validator2+ to connect to validator1)
All validators are added in genesis via --bootstrap-validator
Problem
Each validator seems completely isolated.
They only see themselves and continue voting solo.
The logs show repeated messages like:
[solana_gossip::cluster_info] run_listen timeout, table size: 5
Even validator1 never prints a Gossip: listening on ... message.
Using solana-gossip spy, no nodes appear—each validator seems to think it’s alone.
There’s no invalid entrypoint error anymore, and I’ve confirmed the gossip port on validator1 is open from other containers using bash -c "echo > /dev/tcp/validator1/8003".
Context
I’m aware that Docker on macOS doesn’t support --network=host, so I’m limited to bridge mode.
Each validator is on the same custom bridge network (solana_net) and reachable via container DNS or static IP.
I’ve tried --gossip-host as both the container name and static IP (e.g., validator1 or 172.16.1.2), and I’ve verified that gossip ports are reachable.
I’m using solana-validator v1.18.26.
Questions
Why is the gossip network not forming at all, even though network reachability seems fine?
How can I get the validators to recognize each other in this setup?
Is there a known workaround or configuration tweak specific to macOS Docker environments for running multiple validators?
I’d appreciate any help—especially from anyone who has run a local Solana cluster on macOS with Docker. I can provide full scripts and docker-compose setup if needed.
Heres my docker-compose
version: "3.8"
services:
validator1:
image: my-solana-validator
platform: linux/arm64
container_name: validator1
ports:
- "8899:8899"
- "8001:8001/udp"
environment:
- RUST_LOG=info,solana_gossip=debug
volumes:
- ./entrypoint.sh:/root/entrypoint.sh
- ./config/validator1:/root/.config/solana
- ./config:/config
- ./logs/validator1:/root/logs
command: ["/bin/bash", "/root/entrypoint.sh", "validator1"]
networks:
solana_net:
ipv4_address: 172.16.1.2
validator2:
image: my-solana-validator
platform: linux/arm64
container_name: validator2
environment:
- RUST_LOG=info,solana_gossip=debug
extra_hosts:
- "validator1:172.16.1.2"
depends_on:
- validator1
ports:
- "8898:8898"
- "8002:8002/udp"
volumes:
- ./entrypoint.sh:/root/entrypoint.sh
- ./config/validator2:/root/.config/solana
- ./config:/config
- ./logs/validator2:/root/logs
command: ["/bin/bash", "/root/entrypoint.sh", "validator2"]
networks:
solana_net:
ipv4_address: 172.16.1.3
validator3:
image: my-solana-validator
platform: linux/arm64
container_name: validator3
depends_on:
- validator1
ports:
- "8897:8897"
- "8003:8003/udp"
volumes:
- ./entrypoint.sh:/root/entrypoint.sh
- ./config/validator3:/root/.config/solana
- ./config:/config
- ./logs/validator3:/root/logs
command: ["/bin/bash", "/root/entrypoint.sh", "validator3"]
networks:
solana_net:
ipv4_address: 172.16.1.4
validator4:
image: my-solana-validator
platform: linux/arm64
container_name: validator4
depends_on:
- validator1
ports:
- "8896:8896"
- "8004:8004/udp"
volumes:
- ./entrypoint.sh:/root/entrypoint.sh
- ./config/validator4:/root/.config/solana
- ./config:/config
- ./logs/validator4:/root/logs
command: ["/bin/bash", "/root/entrypoint.sh", "validator4"]
networks:
solana_net:
ipv4_address: 172.16.1.5
networks:
solana_net:
driver: bridge
ipam:
config:
- subnet: 172.16.1.0/24
And...entrypoint.sh
(--entrypoint validator1:8001 is not working for me...)
#!/bin/bash
set -e
NODE=$1
CONFIG_DIR="/root/.config/solana"
LOG_DIR="/root/logs"
LEDGER="$CONFIG_DIR"
SHARED_LEDGER="/config/ledger-share"
TAR_PATH="$SHARED_LEDGER/ledger.tar.gz"
ID="$CONFIG_DIR/identity-keypair.json"
VOTE="$CONFIG_DIR/vote-keypair.json"
STAKE="$CONFIG_DIR/stake-keypair.json"
PORT_BASE=$(echo $NODE | grep -o '[0-9]*')
RPC_PORT=$((8899 - PORT_BASE + 1))
GOSSIP_PORT=$((8001 + PORT_BASE - 1))
PORT_MIN=$((8020 + 10 * PORT_BASE))
PORT_MAX=$((PORT_MIN + 15))
echo "GOSSIP_PORT : ${GOSSIP_PORT}"
mkdir -p "$CONFIG_DIR" "$LOG_DIR" "$SHARED_LEDGER"
# 키 생성
for FILE in "$ID" "$VOTE" "$STAKE"; do
[ ! -f "$FILE" ] && solana-keygen new --no-passphrase -o "$FILE"
done
# validator1: genesis & ledger
if [ "$NODE" = "validator1" ]; then
echo "🧬 Generating genesis for validator1..."
BOOTSTRAP_ARGS=""
for i in {1..4}; do
B_DIR="/root/.config/solana"
[ "$i" != "1" ] && B_DIR="/config/validator$i"
BOOTSTRAP_ARGS+=" --bootstrap-validator \
$(solana-keygen pubkey $B_DIR/identity-keypair.json) \
$(solana-keygen pubkey $B_DIR/vote-keypair.json) \
$(solana-keygen pubkey $B_DIR/stake-keypair.json)"
done
solana-genesis \
--ledger "$LEDGER" \
$BOOTSTRAP_ARGS \
--bootstrap-validator-lamports 100000000000 \
--bootstrap-validator-stake-lamports 100000000000 \
--faucet-pubkey "$(solana-keygen pubkey $ID)" \
--faucet-lamports 100000000000000 \
--hashes-per-tick auto \
--cluster-type development
# --ticks-per-slot 4 \
# --slots-per-epoch 32 \
echo "📦 Compressing ledger (excluding keys)..."
tar --exclude='identity-keypair.json' \
--exclude='vote-keypair.json' \
--exclude='stake-keypair.json' \
-czf "$TAR_PATH" -C "$LEDGER" .
else
echo "📂 Extracting ledger archive for $NODE..."
until [ -f "$TAR_PATH" ]; do
echo "⏳ Waiting for ledger from validator1..."
sleep 1
done
tar -xzf "$TAR_PATH" -C "$LEDGER"
fi
# execute validator
echo "🚀 Starting validator $NODE..."
if [ "$NODE" = "validator1" ]; then
solana-validator \
--identity "$ID" \
--vote-account "$(solana-keygen pubkey $VOTE)" \
--ledger "$LEDGER" \
--rpc-port "$RPC_PORT" \
--gossip-host validator1 \
--gossip-port "$GOSSIP_PORT" \
--dynamic-port-range "$PORT_MIN-$PORT_MAX" \
--enable-rpc-transaction-history \
--enable-cpi-and-log-storage \
--no-wait-for-vote-to-start-leader \
--no-os-network-limits-test \
--full-rpc-api \
--log "$LOG_DIR/validator.log"
else
if [ "$NODE" != "validator1" ]; then
echo "⏳ Waiting for validator1 gossip port to open..."
while ! timeout 1 bash -c "echo > /dev/tcp/validator1/8001" 2>/dev/null; do
sleep 1
done
echo "✅ validator1 gossip port is open"
fi
solana-validator \
--identity "$ID" \
--vote-account "$(solana-keygen pubkey $VOTE)" \
--ledger "$LEDGER" \
--rpc-port "$RPC_PORT" \
--gossip-port "$GOSSIP_PORT" \
--dynamic-port-range "$PORT_MIN-$PORT_MAX" \
--enable-rpc-transaction-history \
--enable-cpi-and-log-storage \
--no-wait-for-vote-to-start-leader \
--no-os-network-limits-test \
--full-rpc-api \
--log "$LOG_DIR/validator.log"
# --entrypoint validator1:8001 \
fi
Quote
Report
Aseneca · External communityPost link
External answer — Solana Stack Exchange
Author: Aseneca
Original post: https://solana.stackexchange.com/a/23207
License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/
Adaptation: HTML converted to plain text; contact email addresses removed.
The problem seems to lie within the
--gossip-host
flag. By default configuration, Solana proposes this port as its public address in gossip
--gossip-host
be set to an IP reacheable by other validators and requires that but in Docker on MacOs,
--gossip-host validator1
resolves inside the container, but peer validators see the wrong address in gossip and cannot connect in return.
I'd recommend that instead of container names like
--gossip-host validator1
, you should give each validator its static IP on the Docker bridge in your configuration. Also ensure you are clearly defining an entry point for all non-bootstrapped validators so that every validator after
validator1
must be told where to bootstrap gossip else they'd never be able to join.
Since you are already calculating
GOSSIP_PORT
from
$NODE
, you have to make sure it matches the one you’re exposing.
Quote
Report
Post Reply
Quoted from Forex.com.bd-Editorial External question — Solana Stack Exchange Author: kangrangx Source score (net votes, not local likes): 1 Original post: https://solana.stackexchange.com/questions/23070 License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/ Adaptation: HTML converted to plain text; contact email addresses removed. I’m a small developer who recently got interested in Solana and started trying things out 😊 Right now, I’m attempting to build a local Solana testnet on macOS using Docker Desktop, just for learning and experimentation. I’m running a local Solana testnet on macOS using Docker Desktop. I’m using Docker Compose to launch 5 validators (validator1 through validator5), each in its own container. validator1 generates the genesis ledger and shares it with the others via a volume mount. The setup uses a custom Docker bridge network with static IPs like 172.16.1.x, and each validator is configured with: --identity, --vote-account, --ledger (all unique per validator) --gossip-host, --gossip-port, and --entrypoint validator1:8003 (for validator2+ to connect to validator1) All validators are added in genesis via --bootstrap-validator Problem Each validator seems completely isolated. They only see themselves and continue voting solo. The logs show repeated messages like: [solana_gossip::cluster_info] run_listen timeout, table size: 5 Even validator1 never prints a Gossip: listening on ... message. Using solana-gossip spy, no nodes appear—each validator seems to think it’s alone. There’s no invalid entrypoint error anymore, and I’ve confirmed the gossip port on validator1 is open from other containers using bash -c "echo > /dev/tcp/validator1/8003". Context I’m aware that Docker on macOS doesn’t support --network=host, so I’m limited to bridge mode. Each validator is on the same custom bridge network (solana_net) and reachable via container DNS or static IP. I’ve tried --gossip-host as both the container name and static IP (e.g., validator1 or 172.16.1.2), and I’ve verified that gossip ports are reachable. I’m using solana-validator v1.18.26. Questions Why is the gossip network not forming at all, even though network reachability seems fine? How can I get the validators to recognize each other in this setup? Is there a known workaround or configuration tweak specific to macOS Docker environments for running multiple validators? I’d appreciate any help—especially from anyone who has run a local Solana cluster on macOS with Docker. I can provide full scripts and docker-compose setup if needed. Heres my docker-compose version: "3.8" services: validator1: image: my-solana-validator platform: linux/arm64 container_name: validator1 ports: - "8899:8899" - "8001:8001/udp" environment: - RUST_LOG=info,solana_gossip=debug volumes: - ./entrypoint.sh:/root/entrypoint.sh - ./config/validator1:/root/.config/solana - ./config:/config - ./logs/validator1:/root/logs command: ["/bin/bash", "/root/entrypoint.sh", "validator1"] networks: solana_net: ipv4_address: 172.16.1.2 validator2: image: my-solana-validator platform: linux/arm64 container_name: validator2 environment: - RUST_LOG=info,solana_gossip=debug extra_hosts: - "validator1:172.16.1.2" depends_on: - validator1 ports: - "8898:8898" - "8002:8002/udp" volumes: - ./entrypoint.sh:/root/entrypoint.sh - ./config/validator2:/root/.config/solana - ./config:/config - ./logs/validator2:/root/logs command: ["/bin/bash", "/root/entrypoint.sh", "validator2"] networks: solana_net: ipv4_address: 172.16.1.3 validator3: image: my-solana-validator platform: linux/arm64 container_name: validator3 depends_on: - validator1 ports: - "8897:8897" - "8003:8003/udp" volumes: - ./entrypoint.sh:/root/entrypoint.sh - ./config/validator3:/root/.config/solana - ./config:/config - ./logs/validator3:/root/logs command: ["/bin/bash", "/root/entrypoint.sh", "validator3"] networks: solana_net: ipv4_address: 172.16.1.4 validator4: image: my-solana-validator platform: linux/arm64 container_name: validator4 depends_on: - validator1 ports: - "8896:8896" - "8004:8004/udp" volumes: - ./entrypoint.sh:/root/entrypoint.sh - ./config/validator4:/root/.config/solana - ./config:/config - ./logs/validator4:/root/logs command: ["/bin/bash", "/root/entrypoint.sh", "validator4"] networks: solana_net: ipv4_address: 172.16.1.5 networks: solana_net: driver: bridge ipam: config: - subnet: 172.16.1.0/24 And...entrypoint.sh (--entrypoint validator1:8001 is not working for me...) #!/bin/bash set -e NODE=$1 CONFIG_DIR="/root/.config/solana" LOG_DIR="/root/logs" LEDGER="$CONFIG_DIR" SHARED_LEDGER="/config/ledger-share" TAR_PATH="$SHARED_LEDGER/ledger.tar.gz" ID="$CONFIG_DIR/identity-keypair.json" VOTE="$CONFIG_DIR/vote-keypair.json" STAKE="$CONFIG_DIR/stake-keypair.json" PORT_BASE=$(echo $NODE | grep -o '[0-9]*') RPC_PORT=$((8899 - PORT_BASE + 1)) GOSSIP_PORT=$((8001 + PORT_BASE - 1)) PORT_MIN=$((8020 + 10 * PORT_BASE)) PORT_MAX=$((PORT_MIN + 15)) echo "GOSSIP_PORT : ${GOSSIP_PORT}" mkdir -p "$CONFIG_DIR" "$LOG_DIR" "$SHARED_LEDGER" # 키 생성 for FILE in "$ID" "$VOTE" "$STAKE"; do [ ! -f "$FILE" ] && solana-keygen new --no-passphrase -o "$FILE" done # validator1: genesis & ledger if [ "$NODE" = "validator1" ]; then echo "🧬 Generating genesis for validator1..." BOOTSTRAP_ARGS="" for i in {1..4}; do B_DIR="/root/.config/solana" [ "$i" != "1" ] && B_DIR="/config/validator$i" BOOTSTRAP_ARGS+=" --bootstrap-validator \ $(solana-keygen pubkey $B_DIR/identity-keypair.json) \ $(solana-keygen pubkey $B_DIR/vote-keypair.json) \ $(solana-keygen pubkey $B_DIR/stake-keypair.json)" done solana-genesis \ --ledger "$LEDGER" \ $BOOTSTRAP_ARGS \ --bootstrap-validator-lamports 100000000000 \ --bootstrap-validator-stake-lamports 100000000000 \ --faucet-pubkey "$(solana-keygen pubkey $ID)" \ --faucet-lamports 100000000000000 \ --hashes-per-tick auto \ --cluster-type development # --ticks-per-slot 4 \ # --slots-per-epoch 32 \ echo "📦 Compressing ledger (excluding keys)..." tar --exclude='identity-keypair.json' \ --exclude='vote-keypair.json' \ --exclude='stake-keypair.json' \ -czf "$TAR_PATH" -C "$LEDGER" . else echo "📂 Extracting ledger archive for $NODE..." until [ -f "$TAR_PATH" ]; do echo "⏳ Waiting for ledger from validator1..." sleep 1 done tar -xzf "$TAR_PATH" -C "$LEDGER" fi # execute validator echo "🚀 Starting validator $NODE..." if [ "$NODE" = "validator1" ]; then solana-validator \ --identity "$ID" \ --vote-account "$(solana-keygen pubkey $VOTE)" \ --ledger "$LEDGER" \ --rpc-port "$RPC_PORT" \ --gossip-host validator1 \ --gossip-port "$GOSSIP_PORT" \ --dynamic-port-range "$PORT_MIN-$PORT_MAX" \ --enable-rpc-transaction-history \ --enable-cpi-and-log-storage \ --no-wait-for-vote-to-start-leader \ --no-os-network-limits-test \ --full-rpc-api \ --log "$LOG_DIR/validator.log" else if [ "$NODE" != "validator1" ]; then echo "⏳ Waiting for validator1 gossip port to open..." while ! timeout 1 bash -c "echo > /dev/tcp/validator1/8001" 2>/dev/null; do sleep 1 done echo "✅ validator1 gossip port is open" fi solana-validator \ --identity "$ID" \ --vote-account "$(solana-keygen pubkey $VOTE)" \ --ledger "$LEDGER" \ --rpc-port "$RPC_PORT" \ --gossip-port "$GOSSIP_PORT" \ --dynamic-port-range "$PORT_MIN-$PORT_MAX" \ --enable-rpc-transaction-history \ --enable-cpi-and-log-storage \ --no-wait-for-vote-to-start-leader \ --no-os-network-limits-test \ --full-rpc-api \ --log "$LOG_DIR/validator.log" # --entrypoint validator1:8001 \ fi
Checking account access…