Skywire Docker Deployment¶
This document describes deploying skywire services using Docker Compose.
For systemd / direct-on-host deployment see DEPLOYMENT_SYSTEMD.md. For Kubernetes notes (illustrative — no production K8s deployment exists today) see KUBERNETES_DEPLOYMENT.md.
All services use the skycoin/skywire:test Docker image, which contains the unified skywire binary.
Prerequisites¶
- Docker and Docker Compose
- A server with a public IP address
- For the STUN server: two distinct public IP addresses bound to the server's network interfaces (see STUN Server)
Minimum Server Specifications¶
- CPU: 2+ cores
- RAM: 4 GB minimum, 8 GB recommended
- Storage: 40 GB SSD
- Network: Public IPv4 address with unrestricted UDP (for STUN and SUDPH)
- Ports: The following ports must be accessible:
- TCP: service ports (9090-9098, configurable via
.env) - UDP: 30178 (address resolver SUDPH), 3478-3479 (STUN)
- TCP: 30081+ (dmsg servers)
Services Overview¶
The compose file defines the following services:
| Service | Command | Default Port | Dependencies | Description |
|---|---|---|---|---|
| address-resolver | svc ar |
9093 (TCP), 30178 (UDP) | redis, dmsg-discovery | Resolves visor addresses for STCPR/SUDPH transports |
| conf-service | svc confbs |
configurable | none | Config bootstrap server for visor configuration |
| dmsg-discovery | dmsg disc |
9090 | redis | DMSG discovery server |
| dmsg-server | dmsg server start |
8080 (internal) | none | DMSG relay server |
| network-monitor | svc nm |
configurable | most services | Monitors network health and cleans stale entries |
| route-finder | svc rf |
9092 | redis, dmsg-discovery, transport-discovery | Finds routes between visors |
| service-discovery | svc sd |
9098 | redis, dmsg-discovery | Service discovery (VPN servers, etc.) |
| setup-node | svc sn |
none | dmsg-discovery | Route setup node |
| transport-discovery | svc tpd |
9091 | redis, dmsg-discovery | Transport discovery and registration |
| transport-setup | svc tps |
none | none | Transport setup service |
| stun | svc stun |
3478, 3479 | none | STUN server for NAT detection |
| geoip | svc ip |
configurable | none | GeoIP service (embedded database) |
| redis | redis image | 6379 | none | Key-value store for service state |
| skywire-visor | visor |
none | most services | Optional deployment visor |
Configuration¶
Environment File (.env)¶
All configuration is done through a .env file in the same directory as compose.yaml. A template is provided as env.template — copy it and fill in the values:
The .env file contains:
- Service ports
- Secret keys for each service (generate with skywire cli config gen-keys)
- Redis credentials
- Network monitor public keys
- DMSG server configuration
- Pprof debug ports
Key Generation¶
Generate a keypair for each service:
Each service that accepts --sk needs its own unique secret key.
Config Bootstrapper¶
The config bootstrapper (svc confbs) serves deployment configuration to visors. It reads from a config.json file mounted at /etc/skywire/config.json. This file should contain the deployment's service URLs, STUN server addresses, and other configuration that visors need.
DMSG Server Configuration¶
Each dmsg server requires its own config.json with a unique keypair. Generate with:
Update the public_address and discovery fields to match your deployment. Mount the config directory as a volume in the compose file.
DMSG server DHT — removed¶
Earlier revisions of this document described enable_dht: true and
redis_addr turning a dmsg-server into a Kademlia DHT full node on dmsg port
100, and recommended it for production. That feature no longer exists.
There is no pkg/dht, no dht CLI subcommand, no dht:* Redis mirror, and
nothing reads enable_dht — setting it does nothing.
Discovery is served by CXO and by the HTTP discovery services. If you are
copying an old config.json, drop enable_dht and redis_addr from the
dmsg-server stanza; the dmsg-server needs no Redis of its own.
Setup Node Configuration¶
The setup node requires a config file with a keypair and DMSG/transport discovery endpoints. Generate with:
STUN Server¶
The STUN server implements RFC 3489 NAT type detection and requires two distinct public IP addresses bound locally on the server.
Why Two IPs?¶
STUN NAT detection works by having the server respond from different IP addresses. The server binds four UDP sockets: - primary IP : primary port (3478) - primary IP : alt port (3479) - secondary IP : primary port (3478) - secondary IP : alt port (3479)
Both IPs must be configured on the server's network interfaces (ip addr should show both).
Configuration¶
The STUN container uses network_mode: "host" to bind directly to the host's network interfaces:
stun:
container_name: stun
image: "skycoin/skywire:test"
network_mode: "host"
restart: always
command:
- svc
- stun
- --primary-ip
- "YOUR_PRIMARY_IP"
- --secondary-ip
- "YOUR_SECONDARY_IP"
- --port
- "3478"
- --alt-port
- "3479"
Verify your server has two public IPs with ip addr. If you only have one, you will need to add a secondary IP through your hosting provider.
Verifying STUN¶
After starting the STUN server, verify it is working:
# Check that the process is listening
ss -ulnp | grep 3478
# Test from a remote machine
skywire cli config gen -r # Should show NAT type in output
GeoIP Service¶
The GeoIP service (svc ip) uses an embedded MaxMind GeoLite2-City database. No external database download is required:
Address Resolver UDP Port¶
The address resolver listens on both TCP (HTTP API) and UDP (SUDPH hole-punching). The UDP port (default 30178) must be mapped correctly:
ports:
- ${AR_PORT}:${AR_PORT} # TCP: HTTP API
- 30178:30178/udp # UDP: SUDPH (must match internal port)
Important: The UDP port mapping must be 30178:30178/udp (not 30178:${AR_PORT}/udp), because the AR's internal KCP/UDP listener binds to port 30178 regardless of the HTTP port.
Deployment¶
- Copy
compose.yamland your.envfile to the deployment server - Create required config directories and files:
config-bootstrapper/config.json— config bootstrapper service configdmsg-server/<name>/config.json— dmsg server configssetup-node/config.json— setup node config- Pull the image and start services:
- Check service health:
- Verify individual services:
curl http://localhost:9090/health # dmsg-discovery
curl http://localhost:9093/health # address-resolver
curl http://localhost:9098/health # service-discovery
Reverse Proxy¶
For production deployments, place services behind a reverse proxy (e.g. Caddy, nginx) with TLS. Example Caddy configuration:
dmsgd.skywire.example.com {
reverse_proxy http://127.0.0.1:9090
}
ar.skywire.example.com {
reverse_proxy http://127.0.0.1:9093
}
rf.skywire.example.com {
reverse_proxy http://127.0.0.1:9092
}
tpd.skywire.example.com {
reverse_proxy http://127.0.0.1:9091
}
sd.skywire.example.com {
reverse_proxy http://127.0.0.1:9098
}
Note: The STUN server and AR UDP port (30178) are not HTTP services and should not be proxied.
Troubleshooting¶
STUN server restart loop¶
- Check
docker logs stun— usually a port conflict with another process (e.g. old coturn) - Run
ss -ulnp | grep 3478to find conflicting processes
SUDPH handshake timeout¶
- Verify the AR UDP port mapping is
30178:30178/udp - Check
docker logs address-resolverfor incoming UDP traffic - Test UDP reachability:
echo "test" | nc -u -w5 <ar-host> 30178
Services not starting¶
- Check
.envfile exists and has all required variables - Check
docker compose logs <service-name> - Verify redis is healthy:
docker compose ps