* squashing localnet-v2 commits (again) cargo fmt fixes to localnet purge provide path in the error message output args log failed exec print based on tty check-prerequisites cmd checked iptables update basic kernel features check enable ipv6 rules add forwarding rules squashing localnet-v2 commits additional changes propagate custom-dns flag to all run containers remove is_mock from EcashManager another localnet squash unused import chore: remove redundant testnet manager missing impl additional linux fixes command to rebuild container image wait for at least 2 blocks additional node startup fixes added --custom-dns flag to nym node setup add gateway probe + wait for DKG magic file fixed localnet down on linux container ls re-enable state resync additional feature locking macos adjustments working nyxd startup on linux wip linux box wip separating network inspect betweewn macos and linux initial linux feature locking moved all container commands into a single location finally working initial node performance squashing orchestrator commits cleanup fixed condition for naive rearrangement added cache of cosmwasm contracts for speed up on subsequent runs 'down' command refreshing described cache after nodes are bonded nym nodes setup + wip on nym api refresh nodes setup WIP first pass cleanup placeholder for nym-node setup bypassing the dkg further progress on nym-api setup wip: api setup up/down/purge placeholders persisting contract setup data fix contract upload by forcing amd64 container platform wip: contracts setup4 wip: contracts setup3 wip: contracts setup2 wip: contracts setup include network setup init and spawn nyxd build nyxd image in dedicated orchestrator build nyxd image squashed cherry-picked lp changes Bits and bobs to make everything work Title MacOS setup instructions Docker/Container localnet * clippy * fixes on non-unix targets --------- Co-authored-by: durch <durch@users.noreply.github.com>
Nym Localnet for Kata Container Runtimes
A complete Nym mixnet test environment running on Apple's container runtime for macOS (for now).
Overview
This localnet setup provides a fully functional Nym mixnet for local development and testing:
- 3 mixnodes (layer 1, 2, 3)
- 1 gateway (entry + exit mode)
- 1 network-requester (service provider)
- 1 SOCKS5 client
All components run in isolated containers with proper networking and dynamic IP resolution.
Prerequisites
Required
- macOS (tested on macOS Sequoia 15.0+)
- Apple Container Runtime - Built into macOS
- Docker Desktop (for building images only)
- Python 3 with
base58library
Installation
# Install Python dependencies
pip3 install --break-system-packages base58
# Verify container runtime is available
container --version
# Verify Docker is installed (for building)
docker --version
Quick Start
# Navigate to the localnet directory
cd docker/localnet
# Build the container image
./localnet.sh build
# Start the localnet
./localnet.sh start
# Test the SOCKS5 proxy
curl -L --socks5 localhost:1080 https://nymtech.net
# View logs
./localnet.sh logs gateway
./localnet.sh logs socks5
# Stop the localnet
./localnet.sh stop
# Clean up everything
./localnet.sh clean
Architecture
Container Network
All containers run on a custom bridge network (nym-localnet-network) with dynamic IP assignment:
Host Machine (macOS)
├── nym-localnet-network (bridge)
│ ├── nym-mixnode1 (192.168.66.3)
│ ├── nym-mixnode2 (192.168.66.4)
│ ├── nym-mixnode3 (192.168.66.5)
│ ├── nym-gateway (192.168.66.6)
│ ├── nym-network-requester (192.168.66.7)
│ └── nym-socks5-client (192.168.66.8)
Ports published to host:
- 1080 → SOCKS5 proxy
- 9000/9001 → Gateway entry ports
- 10001-10005 → Mixnet ports
- 20001-20005 → Verloc ports
- 30001-30005 → HTTP APIs
- 41264/41265 → LP control ports (registration)
- 51822/51823 → WireGuard tunnel ports (gateway/gateway2)
Startup Flow
-
Container Initialization (parallel)
- Each container starts and gets a dynamic IP
- Each node runs
nym-node run --init-onlywith its container IP - Bonding JSON files are written to shared volume
-
Topology Generation (sequential)
- Wait for all 4 bonding JSON files
- Get container IPs dynamically
- Run
build_topology.pywith container IPs - Generate
network.jsonwith correct addresses
-
Node Startup (parallel)
- Each container starts its node with
--localflag - Nodes read configuration from init phase
- Clients use custom topology file
- Each container starts its node with
-
Service Providers (sequential)
- Network requester initializes and starts
- SOCKS5 client initializes with requester address
Network Topology
The network.json file contains the complete network topology:
{
"metadata": {
"key_rotation_id": 0,
"absolute_epoch_id": 0,
"refreshed_at": "2025-11-03T..."
},
"rewarded_set": {
"epoch_id": 0,
"entry_gateways": [4],
"exit_gateways": [4],
"layer1": [1],
"layer2": [2],
"layer3": [3],
"standby": []
},
"node_details": {
"1": { "mix_host": "192.168.66.3:10001", ... },
"2": { "mix_host": "192.168.66.4:10002", ... },
"3": { "mix_host": "192.168.66.5:10003", ... },
"4": { "mix_host": "192.168.66.6:10004", ... }
}
}
Commands
Build
./localnet.sh build
Builds the Docker image and loads it into Apple container runtime.
Note: First build takes ~5-10 minutes to compile all components.
Start
./localnet.sh start
Starts all containers, generates topology, and launches the complete network.
Expected output:
[INFO] Starting Nym Localnet...
[SUCCESS] Network created: nym-localnet-network
[INFO] Starting nym-mixnode1...
[SUCCESS] nym-mixnode1 started
...
[INFO] Building network topology with container IPs...
[SUCCESS] Network topology created successfully
[SUCCESS] Nym Localnet is running!
Test with:
curl -x socks5h://127.0.0.1:1080 https://nymtech.net
Stop
./localnet.sh stop
Stops and removes all running containers.
Clean
./localnet.sh clean
Complete cleanup: removes containers, volumes, network, and temporary files.
Logs
# View logs for a specific container
./localnet.sh logs <container-name>
# Container names:
# - mix1, mix2, mix3
# - gateway
# - requester
# - socks5
# Examples:
./localnet.sh logs gateway
./localnet.sh logs socks5
container logs nym-gateway --follow
Status
# List all containers
container list
# Check specific container
container logs nym-gateway
# Inspect network
container network inspect nym-localnet-network
Testing
Basic SOCKS5 Test
# Simple HTTP request with redirect following
curl -L --socks5 localhost:1080 http://example.com
# HTTPS request
curl -L --socks5 localhost:1080 https://nymtech.net
# Download a file
curl -L --socks5 localhost:1080 \
https://test-download-files-nym.s3.amazonaws.com/download-files/1MB.zip \
--output /tmp/test.zip
Verify Network Topology
# View the generated topology
container exec nym-gateway cat /localnet/network.json | jq .
# Check container IPs
container list | grep nym-
# Verify all bonding files exist
container exec nym-gateway ls -la /localnet/
Test Mixnet Routing
# All traffic flows through: client → mix1 → mix2 → mix3 → gateway → internet
# Watch logs to verify routing:
container logs nym-mixnode1 --follow &
container logs nym-mixnode2 --follow &
container logs nym-mixnode3 --follow &
container logs nym-gateway --follow &
# Make a request
curl -L --socks5 localhost:1080 https://nymtech.com
LP (Lewes Protocol) Testing
The gateway is configured with LP listener enabled and mock ecash verification for testing:
# LP listener ports (exposed on host):
# - 41264: LP control port (TCP registration)
# - 51264: LP data port
# Check LP ports are listening
nc -zv localhost 41264
nc -zv localhost 51264
# Test LP registration with nym-gateway-probe
cargo run -p nym-gateway-probe run-local \
--mnemonic "test mnemonic here" \
--gateway-ip 'localhost:41264' \
--only-lp-registration
Mock Ecash Mode:
- Gateway uses
--lp.use-mock-ecash trueflag - Accepts ANY bandwidth credential without blockchain verification
- Perfect for testing LP protocol implementation
- WARNING: Never use mock ecash in production!
Testing without blockchain: The mock ecash manager allows testing the complete LP registration flow without requiring:
- Running nyxd blockchain
- Deploying smart contracts
- Acquiring real bandwidth credentials
- Setting up coconut signers
This makes localnet perfect for rapid LP protocol development and testing.
File Structure
docker/localnet/
├── README.md # This file
├── localnet.sh # Main orchestration script
├── Dockerfile.localnet # Docker image definition
└── build_topology.py # Topology generator
How It Works
Node Initialization
Each node initializes itself at runtime inside its container:
# Get container IP
CONTAINER_IP=$(hostname -i)
# Initialize with container IP
nym-node run --id mix1-localnet --init-only \
--unsafe-disable-replay-protection \
--local \
--mixnet-bind-address=0.0.0.0:10001 \
--verloc-bind-address=0.0.0.0:20001 \
--http-bind-address=0.0.0.0:30001 \
--http-access-token=lala \
--public-ips $CONTAINER_IP \
--output=json \
--bonding-information-output="/localnet/mix1.json"
Key flags:
--local: Accept private IPs for local development--public-ips: Announce the container's IP address--unsafe-disable-replay-protection: Disable bloomfilter to save memory
Dynamic Topology
The topology is built after containers start:
# Get container IPs
MIX1_IP=$(container exec nym-mixnode1 hostname -i)
MIX2_IP=$(container exec nym-mixnode2 hostname -i)
MIX3_IP=$(container exec nym-mixnode3 hostname -i)
GATEWAY_IP=$(container exec nym-gateway hostname -i)
# Build topology with actual IPs
python3 build_topology.py /localnet localnet \
$MIX1_IP $MIX2_IP $MIX3_IP $GATEWAY_IP
This ensures the topology contains reachable container addresses.
Client Configuration
Clients use --custom-mixnet to read the local topology:
# Network requester
nym-network-requester init \
--id "network-requester-$SUFFIX" \
--open-proxy=true \
--custom-mixnet /localnet/network.json
# SOCKS5 client
nym-socks5-client init \
--id "socks5-client-$SUFFIX" \
--provider "$REQUESTER_ADDRESS" \
--custom-mixnet /localnet/network.json \
--host 0.0.0.0
The --custom-mixnet flag tells clients to use our local topology instead of fetching from nym-api.
Troubleshooting
Container Build Issues
Problem: Docker build fails
# Check Docker is running
docker info
# Clean Docker cache
docker system prune -a
# Rebuild with no cache
./localnet.sh build
Problem: Container image load fails
# Verify temp file was created
ls -lh /tmp/nym-localnet-image-*
# Check container runtime
container image list
# Manually load if needed
docker save -o /tmp/nym-image.tar nym-localnet:latest
container image load --input /tmp/nym-image.tar
Network Issues
Problem: Containers can't communicate
# Check network exists
container network list | grep nym-localnet
# Inspect network
container network inspect nym-localnet-network
# Verify containers are on the network
container list | grep nym-
Problem: SOCKS5 connection refused
# Check SOCKS5 is listening
container logs nym-socks5-client | grep "Listening on"
# Verify port mapping
container list | grep socks5
# Test from host
nc -zv localhost 1080
Node Issues
Problem: "No valid public addresses" error
- Ensure
--localflag is present in both init and run commands - Check container can resolve its own IP:
container exec nym-mixnode1 hostname -i - Verify
--public-ipsis using$CONTAINER_IPvariable
Problem: "TUN device error"
- The gateway needs TUN device support for exit functionality
- Verify
iproute2is installed in the image (addsipcommand) - Check gateway logs:
container logs nym-gateway - The gateway should show: "Created TUN device: nymtun0"
Problem: "Noise handshake" warnings
- These are warnings, not errors - nodes fall back to TCP
- Does not affect functionality in local development
- Safe to ignore for testing purposes
Topology Issues
Problem: Network.json not created
# Check all bonding files exist
container exec nym-gateway ls -la /localnet/
# Verify build_topology.py ran
container logs nym-gateway | grep "Building network topology"
# Check Python dependencies
container exec nym-gateway python3 -c "import base58"
Problem: Clients can't connect to nodes
# Verify IPs in topology match container IPs
container exec nym-gateway cat /localnet/network.json | jq '.node_details'
container list | grep nym-
# Check containers can reach each other
container exec nym-socks5-client ping -c 1 192.168.66.6
Startup Issues
Problem: Containers exit immediately
# Check logs for errors
container logs nym-mixnode1
# Common issues:
# - Missing network.json: Wait for topology to be built
# - Port already in use: Check for conflicting services
# - Init failed: Check for correct container IP
Problem: Topology build times out
# Verify all containers initialized
container exec nym-gateway ls -la /localnet/*.json
# Check for init errors
container logs nym-mixnode1 | grep -i error
# Manual cleanup and restart
./localnet.sh clean
./localnet.sh start
Performance Notes
Memory Usage
- Each mixnode: ~200MB
- Gateway: ~300MB (includes TUN device)
- Network requester: ~150MB
- SOCKS5 client: ~150MB
- Total: ~1.2GB + overhead
Recommended: 4GB+ system memory
Startup Time
- Image build: ~5-10 minutes (first time)
- Network start: ~20-30 seconds
- Node initialization: ~5-10 seconds per node (parallel)
Latency
Mixnet adds latency by design for privacy:
- ~1-3 seconds for SOCKS5 requests
- Cover traffic adds random delays
- Local testing may show variable timing
This is expected behavior - the mixnet provides privacy through traffic mixing.
Advanced Configuration
Custom Node Configuration
Edit node init commands in localnet.sh (search for nym-node run --init-only):
# Example: Change mixnode ports
--mixnet-bind-address=0.0.0.0:11001 \
--verloc-bind-address=0.0.0.0:21001 \
--http-bind-address=0.0.0.0:31001 \
Remember to update port mappings in the container run command as well.
Enable Replay Protection
Remove --unsafe-disable-replay-protection flags (requires more memory):
# In start_mixnode() and start_gateway() functions
nym-node run --id mix1-localnet --init-only \
--local \
--mixnet-bind-address=0.0.0.0:10001 \
# ... other flags (without --unsafe-disable-replay-protection)
Note: Each node will require an additional ~1.5GB memory for bloomfilter.
API Access
Each node exposes an HTTP API:
# Get gateway info
curl -H "Authorization: Bearer lala" http://localhost:30004/api/v1/gateway
# Get mixnode stats
curl -H "Authorization: Bearer lala" http://localhost:30001/api/v1/stats
# Get node description
curl -H "Authorization: Bearer lala" http://localhost:30001/api/v1/description
Access token is lala (configured with --http-access-token=lala).
Add More Mixnodes
To add a 4th mixnode:
- Update constants in
localnet.sh:
MIXNODE4_CONTAINER="nym-mixnode4"
- Add start call in
start_all():
start_mixnode 4 "$MIXNODE4_CONTAINER"
-
Update topology builder to include the new node
-
Rebuild and restart:
./localnet.sh clean
./localnet.sh build
./localnet.sh start
Technical Details
Container Runtime
Apple's container runtime is a native macOS container system:
- Uses Virtualization.framework for isolation
- Lightweight VMs for each container
- Native macOS integration
- Separate image store from Docker
- Natively uses Kata Containers images
Initial setup for Container Runtime
- MUST have MacOS Tahoe for inter-container networking
brew install --cask container- Download Kata Containers 3.20, this one can be loaded by
containerand hasCONFIG_TUN=ykernel flaghttps://github.com/kata-containers/kata-containers/releases/download/3.20.0/kata-static-3.20.0-arm64.tar.xz
- Load new kernel
container system kernel set --tar kata-static-3.20.0-arm64.tar.xz --binary opt/kata/share/kata-containers/vmlinux-6.12.42-162
- Validate kernel version once you have container running
uname -rshould return6.12.42cat /proc/config.gz | grep CONFIG_TUNshould returnCONFIG_TUN=y
Image Building
Images are built with Docker then transferred:
docker buildcreates the imagedocker saveexports to tar filecontainer image loadimports into container runtime- Temporary file is cleaned up
This approach allows using Docker's build cache while running on Apple's runtime.
Network Architecture
The custom bridge network (nym-localnet-network):
- Provides container-to-container communication
- Assigns dynamic IPs from 192.168.66.0/24
- NAT for outbound internet access
- Port publishing for host access
Volumes
Two types of volumes:
- Shared data (
/tmp/nym-localnet-*): Bonding files and topology - Node configs (
/tmp/nym-localnet-home-*): Node configurations
Both are ephemeral by default (cleaned up on stop).
Known Limitations
- macOS only: Apple container runtime requires macOS
- No Docker Compose: Uses custom orchestration script
- Dynamic IPs: Container IPs may change between restarts
- Port conflicts: Cannot run alongside services using same ports
- TUN device: Gateway requires
ipcommand for network interfaces
Support
For issues and questions:
- GitHub Issues: https://github.com/nymtech/nym/issues
- Documentation: https://nymtech.net/docs
- Discord: https://discord.gg/nym
License
This localnet setup is part of the Nym project and follows the same license.