Repository navigation
Docker Deployment
This page keeps the copy-paste Docker examples from the README in one place. Use either docker run or Docker Compose, depending on how you prefer to manage containers.
Important
Host-Side vs. In-Container Tooling:
-
urnet-dockerruns on the Docker host (outside the containers). It discovers provider containers, reads their in-container JWTs, and dispatches management tasks directly. -
urnet-toolsruns inside the container (accessible viadocker exec -it <container> urnet-tools <command>).
Install urnet-docker once on the host (SHA-256 verified against the release API):
curl -fSsL https://raw.githubusercontent.com/full-bars/meso-miner/refs/heads/main/scripts/install-urnet-docker.sh | sh
# installs /usr/local/bin/urnet-docker (or ~/.local/bin when not root)The tool is self-updating afterwards:
urnet-docker update # update the tool binary itself
urnet-docker update --unit urfix # update a provider container in place (no recreate)
urnet-tools self-update # same, for the process/systemd toolCommon host-side commands:
urnet-docker providers # list provider containers
urnet-docker status --unit urfix # status of one container
urnet-docker direct status --unit urfix # show direct IP providing state
urnet-docker direct off --unit urfix # toggle direct IP (proxies only)
urnet-docker usage --unit urfix # show aggregate billable vs control traffic
urnet-docker usage graphs --unit urfix # time-series usage graphs (day/hour/month)
urnet-docker proxy add --unit urfix ~/p.txt # add proxies from host (or URL)
urnet-docker proxy paste --unit urfix < p.txt # stream raw proxies from stdin
urnet-docker proxy trim --unit urfix 500 # hold running proxies at cap (A-F worst first)
urnet-docker proxy refresh --unit urfix # reload proxies without restart
urnet-docker restart --unit urfix # restart a container
urnet-docker logs --unit urfix 100 # stream logs (RAMLOGS-aware)Note
urnet-docker update with a target flag (such as --unit) updates a provider container in place. The container ID stays the same. Plain urnet-docker update with no target updates only the host tool binary. Containers can also be updated by pulling a new image and recreating the container (e.g. via Docker Compose or Watchtower).
Primary image:
ghcr.io/full-bars/meso-miner:latest
The JWT is stored inside the container at /root/.urnetwork/jwt. Without a persistent volume, every container restart wipes it and forces re-authentication using the original auth code, which is single-use and will fail on the second attempt.
All examples below mount a config volume at /root/.urnetwork. With this volume in place, the startup script detects the existing JWT and skips authentication on later starts. Auth codes are only consumed once: on first run or after manually removing the config volume.
The examples below use urfix as the container name.
docker run -d --name urfix \
-v ~/.urnetwork:/root/.urnetwork \
-v /path/to/proxy.txt:/app/proxy.txt \
-e PROXY_URL='https://example.com/your-proxy-list.txt' \
-e URNETWORK_PROXY_BENCHMARK=true \
-e URNETWORK_PROXY_BENCHMARK_ENDPOINT=connect.bringyour.com:443 \
ghcr.io/full-bars/meso-miner:latest| Env var | Purpose |
|---|---|
PROXY_URL |
Live proxy list URL, fetched and merged on interval (see Proxy URL Sources) |
URNETWORK_PROXY_BENCHMARK=true |
Enables per-proxy latency probes (TCP connect every 5m, SOCKS5 every 15m) |
URNETWORK_PROXY_BENCHMARK_ENDPOINT |
Target for SOCKS5 CONNECT probe (default connect.bringyour.com:443) |
For additional containers, change the container name and volumes together.
docker run -d \
--name=urfix \
--pull=always \
--restart=unless-stopped \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--sysctl net.ipv4.ip_forward=1 \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
-e BUILD=jwt \
-e HOST_HOSTNAME=$(hostname) \
-v urfix_config:/root/.urnetwork \
-v /path/to/proxy.txt:/app/proxy.txt \
ghcr.io/full-bars/meso-miner:latest AUTH_CODE_HEREReplace AUTH_CODE_HERE with your token from ur.io. Auth codes are single-use; the token is saved to the urfix_config volume on first run and reused on later starts.
Alternative method:
-e URNETWORK_AUTH_CODE=YOUR_CODETo label this server in webhook alerts and health logs, add:
-e URNETWORK_NODE_NAME=your-server-namedocker run -d \
--name=urfix \
--pull=always \
--restart=unless-stopped \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--sysctl net.ipv4.ip_forward=1 \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
-e BUILD=stable \
-e USER_AUTH=you@example.com \
-e PASSWORD=yourpassword \
-e HOST_HOSTNAME=$(hostname) \
-v urfix_config:/root/.urnetwork \
-v /path/to/proxy.txt:/app/proxy.txt \
ghcr.io/full-bars/meso-miner:latestThe commands are identical to GHCR except for the image name.
docker run -d \
--name=urfix \
--pull=always \
--restart=unless-stopped \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--sysctl net.ipv4.ip_forward=1 \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
-e BUILD=jwt \
-e HOST_HOSTNAME=$(hostname) \
-v urfix_config:/root/.urnetwork \
-v /path/to/proxy.txt:/app/proxy.txt \
ghcr.io/full-bars/meso-miner:latest AUTH_CODE_HEREAlternative method:
-e URNETWORK_AUTH_CODE=YOUR_CODEdocker run -d \
--name=urfix \
--pull=always \
--restart=unless-stopped \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--sysctl net.ipv4.ip_forward=1 \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
-e BUILD=stable \
-e USER_AUTH=you@example.com \
-e PASSWORD=yourpassword \
-e HOST_HOSTNAME=$(hostname) \
-v urfix_config:/root/.urnetwork \
-v /path/to/proxy.txt:/app/proxy.txt \
ghcr.io/full-bars/meso-miner:latestFor another single-container deployment on the same host, copy your docker-compose.yml to a new folder and replace the urfix prefix with a unique name in container_name, volumes, and the top-level volumes: section.
For 3, 5, or 10 nodes in one Compose file, use the Multi-Container Scaling guide.
services:
urnetwork:
image: ghcr.io/full-bars/meso-miner:latest
container_name: urfix
restart: unless-stopped
pull_policy: always
cap_add:
- NET_ADMIN
- NET_RAW
sysctls:
- net.ipv4.ip_forward=1
environment:
- BUILD=jwt
- HOST_HOSTNAME=${HOSTNAME}
volumes:
- urfix_config:/root/.urnetwork
- ./proxy.txt:/app/proxy.txt
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
urfix_config:On first start, provide your auth code using one of these methods:
- Trailing argument:
docker compose run --rm urnetwork AUTH_CODE_HERE - Environment variable: add
URNETWORK_AUTH_CODE=AUTH_CODE_HEREto theenvironment:section, then rundocker compose up -d
After the JWT is saved to the volume, subsequent starts need no auth code:
docker compose up -dservices:
urnetwork:
image: ghcr.io/full-bars/meso-miner:latest
container_name: urfix
restart: unless-stopped
pull_policy: always
cap_add:
- NET_ADMIN
- NET_RAW
sysctls:
- net.ipv4.ip_forward=1
environment:
- BUILD=stable
- USER_AUTH=you@example.com
- PASSWORD=yourpassword
- HOST_HOSTNAME=${HOSTNAME}
volumes:
- urfix_config:/root/.urnetwork
- ./proxy.txt:/app/proxy.txt
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
urfix_config:Setting URNETWORK_RAMLOGS=1 redirects provider logs to /dev/shm/urnetwork.log inside the container, a RAM-backed filesystem, instead of stdout. This keeps log I/O entirely off disk.
Tip
Live Monitoring (RAMLOGS)
If you have URNETWORK_RAMLOGS=1 enabled, your logs are stored in high-speed memory instead of standard output. Use this command to live-tail them:
docker exec -it <container_name> tail -f /dev/shm/urnetwork.logNote
URNETWORK_RAMLOGS=1 and Docker --log-opt are mutually exclusive. When RAM logging is active, nothing is written to stdout, so Docker's log driver has nothing to capture. Remove --log-driver and --log-opt if you enable this.
Example Docker Run:
docker run -d \
--name=urfix \
--pull=always \
--restart=unless-stopped \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--sysctl net.ipv4.ip_forward=1 \
-e URNETWORK_RAMLOGS=1 \
-e URNETWORK_NODE_NAME=urfix \
-e BUILD=jwt \
-e ENABLE_VNSTAT=true \
-e HOST_HOSTNAME=$(hostname) \
-e PROXY_URL='https://example.com/your-proxy-list.txt' \
-v urfix_config:/root/.urnetwork \
-v urfix_vnstat:/var/lib/vnstat \
-v /path/to/proxy.txt:/app/proxy.txt \
-p 9001:8080 \
ghcr.io/full-bars/meso-miner:latest YOUR_AUTH_CODEView logs live:
docker exec -it urfix tail -f /dev/shm/urnetwork.logRAM logs are capped at 1MB with automatic rotation and are lost when the container restarts.
View a single-pane fleet overview showing proxy counts by source (file vs URL), health breakdown, and URL cache status:
docker exec -it <container> provider proxy summaryWarning
Deprecated (v31.3+): Bandwidth hub reporting has been removed. The report_url file and urnet-tools report command are no longer functional. This section is retained for historical reference.
Set or check the hub report URL at runtime without restarting. Uses ~/.urnetwork/report_url inside the container:
# Set report URL
docker exec -it <container> sh -c 'echo "http://HUB_IP:8080" > "$HOME/.urnetwork/report_url"'
# Check current URL
docker exec -it <container> sh -c 'cat "$HOME/.urnetwork/report_url" 2>/dev/null || echo "not set"'
# Disable
docker exec -it <container> sh -c 'rm -f "$HOME/.urnetwork/report_url"'Or use the Go urnet-docker binary (v3.23.0-fix.27.0+) which handles docker exec transparently — it discovers provider containers and delegates commands into them:
urnet-docker report http://HUB_IP:8080
urnet-docker report
urnet-docker report off(The legacy PowerShell wrapper urnet-tools.ps1 has been retired; the Go binary replaces it on every platform.)
Client JWTs are reused across restarts by default (no env var needed since v3.23.0-fix.26). The write path has been un-gated since v25.15, so JWTs were already being saved on every auth cycle — now the read path follows suit.
Disabling:
docker run -d \
--name=urfix \
--pull=always \
--restart=unless-stopped \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--sysctl net.ipv4.ip_forward=1 \
-e BUILD=jwt \
-e URNETWORK_HOT_RESTART=0 \
-v urfix_config:/root/.urnetwork \
-v urfix_vnstat:/var/lib/vnstat \
-v /path/to/proxy.txt:/app/proxy.txt \
ghcr.io/full-bars/meso-miner:latest YOUR_AUTH_CODEStatus check:
docker exec urfix sh -c 'echo ${URNETWORK_HOT_RESTART:-1}'Note
To opt out, set -e URNETWORK_HOT_RESTART=0 at container creation. If you don't set anything, hot-restart is active by default.
Export a provider's full identity state (client JWTs, account JWT, signing keys, proxy lists) as an encrypted, portable bundle for cross-machine transfer.
Save (two steps: save inside container, then copy out):
docker exec -it urfix urnet-tools session save /root/.urnetwork/nyc.urnsession
docker cp urfix:/root/.urnetwork/nyc.urnsession .Load (two steps: copy in, then load inside container):
docker cp atlanta.urnsession urfix:/root/.urnetwork/
docker exec -it urfix urnet-tools session load /root/.urnetwork/atlanta.urnsessionThe load will prompt for the passphrase, check the network_id against the current account, backup existing files, stage the new session, and ask "Restart now? (Y/n)". If yes, it kills the provider process — the container's start script crash loop picks it up with the new identity.
Important
Save and load require an interactive TTY (docker exec -it, not just docker exec). The script will fail with a clear error if -it is omitted.
You can view the full list of never-up and dropped proxies, as well as a live event log of proxy state transitions. These files persist on the config volume and survive container restarts, even if RAM logging is active.
- Persistent (always):
docker exec -it <container> proxy-health - Live-tail RAMLOGS on:
docker exec -it <container> sh -c "tail -f /dev/shm/urnetwork.log | grep -E '\[health\]\[proxies\]|\[pulse\]'" - Live-tail RAMLOGS off:
docker logs -f <container> 2>&1 | grep -E '\[health\]\[proxies\]|\[pulse\]'
The Auto-Tune feature (URNETWORK_PROFILE=auto) automatically optimizes the provider based on server hardware.
It detects total system RAM, assigns buffer sizes, enables Eco mode on very low-RAM machines, and benchmarks disk speed on startup. If storage is too slow, it can automatically enable RAM logging to prevent disk I/O from bottlenecking the network stack.
Example Docker Run:
docker run -d \
--name=urfix \
--pull=always \
--restart=unless-stopped \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--sysctl net.ipv4.ip_forward=1 \
-e BUILD=jwt \
-e URNETWORK_PROFILE=auto \
-e URNETWORK_NODE_NAME=urfix \
-e ENABLE_VNSTAT=true \
-e HOST_HOSTNAME=$(hostname) \
-e PROXY_URL='https://example.com/your-proxy-list.txt' \
-v urfix_config:/root/.urnetwork \
-v urfix_vnstat:/var/lib/vnstat \
-v /path/to/proxy.txt:/app/proxy.txt \
-p 9001:8080 \
ghcr.io/full-bars/meso-miner:latest YOUR_AUTH_CODENote
Because Auto-Tune may enable RAM logging if it detects a slow disk, omit --log-driver and --log-opt from this command to avoid conflicts.
Watchtower can automatically pull new image versions and restart your container when an update is published. Add it to your docker-compose.yml alongside the urnetwork service:
watchtower:
image: containrrr/watchtower
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --cleanup --interval 3600 urfixImportant
The urfix_config volume is required when using Watchtower. Without it, Watchtower will pull a new image, recreate the container, and the auth code will be consumed again, which fails because auth codes are single-use. With the volume mounted, the existing JWT is reused.
JWT Smart Refresh: As of v3.23.0-fix.17, the container includes "smart refresh" logic. If the JWT stored in your volume expires, the provider will automatically detect this, delete the stale file, and attempt to re-authenticate using the USER_AUTH and PASSWORD environment variables if provided. This ensures your nodes stay online even if a JWT is revoked or corrupted during an update.
Note
Pelican Panel deployments: When the image runs under a Pelican panel with PELICAN=yes, the self-update scripts are disabled. The panel manages updates by re-pulling the published image. Runtime fetches are blocked to prevent silently replacing the audited fork binary mid-flight.
As of v3.23.0-fix.26.5, urnet-tools idle-update lets you apply a pending provider update without interrupting active client sessions — it waits for a quiet traffic window before swapping the binary, instead of updating immediately and cutting off whatever's in flight:
docker exec -it <container> urnet-tools idle-updateIt polls billable_rate every 10s and waits until traffic stays at or below a threshold for a sustained window, then double-checks with 1s polling for 10s before actually applying the update — so a brief lull doesn't trigger an update while traffic is still fluctuating.
| Flag | Default | Meaning |
|---|---|---|
--threshold <bytes/sec> |
5120 (5 KiB/s) |
Traffic at or below this is considered "quiet". |
--window <seconds> |
300 (5 min) |
How long traffic must stay quiet before updating. |
# Wait for a 10-minute quiet window under 10 KiB/s
docker exec -it <container> urnet-tools idle-update --window 600 --threshold 10240
# Skip waiting entirely and update immediately
docker exec -it <container> urnet-tools idle-update --window 0Note
If billable_rate isn't available yet (provider predates this feature, or hasn't written its first sample), idle-update treats the node as not idle rather than assuming it's safe to update — it fails closed.
By default, production Docker deployments run with zero listening ports and vnStat disabled. The provider establishes all p2p tunnels and relay connections outbound, requiring no inbound port forwarding.
If you specifically want the web-based vnStat traffic monitor to view real-time throughput graphs:
- Set
-e ENABLE_VNSTAT=true - Map a host port to container port
8080:-p <host_port>:8080 - Mount a persistent volume to preserve traffic history across restarts:
-v <name>_vnstat:/var/lib/vnstat
docker run -d \
--name=urfix \
--pull=always \
--restart=unless-stopped \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--sysctl net.ipv4.ip_forward=1 \
-e BUILD=jwt \
-e ENABLE_VNSTAT=true \
-e HOST_HOSTNAME=$(hostname) \
-v urfix_config:/root/.urnetwork \
-v urfix_vnstat:/var/lib/vnstat \
-v /path/to/proxy.txt:/app/proxy.txt \
-p 127.0.0.1:9001:8080 \
ghcr.io/full-bars/meso-miner:latest AUTH_CODE_HEREAccess the traffic page locally at http://localhost:9001 (bind to 127.0.0.1 prevents exposing the unauthenticated vnStat web UI to the public internet; use a reverse proxy or SSH tunnel if accessing remotely).
When running more than one provider container on the same host with vnStat enabled, you must adhere to three mandatory isolation rules:
Caution
1. Offset Host Ports: The internal container port is always :8080. Every container running on the same Docker host must map to a unique host port (e.g. Node 1 on 127.0.0.1:9001, Node 2 on 127.0.0.1:9002). Attempting to reuse the same host port will cause a Docker daemon bind error (bind: address already in use).
2. Offset vnStat Storage Volumes: Never point multiple containers at the same vnStat volume. vnStat maintains an active database inside /var/lib/vnstat. Sharing a single volume causes concurrent write locks, data clobbering, and database corruption. Each container must have its own isolated volume (e.g. urfix-1_vnstat, urfix-2_vnstat).
3. Isolate Provider Config Volumes (/root/.urnetwork): Never share /root/.urnetwork between multiple running containers. The provider maintains process-local SQLite databases (proxy.state, .client_jwts.json, lifetime) and identity keys inside this directory. Sharing it across containers leads to lock contention and state corruption. Each container requires its own config volume (ur_config_1, ur_config_2).
services:
node-1:
image: ghcr.io/full-bars/meso-miner:latest
container_name: urfix-1
environment:
- BUILD=jwt
- ENABLE_VNSTAT=true
volumes:
- ur_config_1:/root/.urnetwork # ISOLATED identity & state volume
- urfix-1_vnstat:/var/lib/vnstat # DEDICATED vnStat volume
- ./proxy-1.txt:/app/proxy.txt
ports:
- "127.0.0.1:9001:8080" # OFFSET host port (localhost bound)
node-2:
image: ghcr.io/full-bars/meso-miner:latest
container_name: urfix-2
environment:
- BUILD=jwt
- ENABLE_VNSTAT=true
volumes:
- ur_config_2:/root/.urnetwork # ISOLATED identity & state volume
- urfix-2_vnstat:/var/lib/vnstat # DEDICATED vnStat volume
- ./proxy-2.txt:/app/proxy.txt
ports:
- "127.0.0.1:9002:8080" # OFFSET host port (localhost bound)
volumes:
ur_config_1:
ur_config_2:
urfix-1_vnstat:
urfix-2_vnstat:As of v31.2, a docker-compose.monitoring.yml file is included in the repository for a ready-made Prometheus + Grafana stack that scrapes the provider's built-in /metrics endpoint on port 9091:
# Start the provider + monitoring stack together
docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -dThis brings up:
-
Prometheus — configured to scrape
http://urnetwork:9091/metricsat a 15s interval -
Grafana — pre-loaded with a provider dashboard (default login
admin/admin)
Tip
If you run multiple provider containers, update the Prometheus scrape targets in docker-compose.monitoring.yml to point at each container's metrics port. Each container needs a unique host-side port mapping (e.g. -p 9091:9091 on the first, -p 9092:9091 on the second).
The metrics endpoint is enabled by default — no environment variables are needed. To disable it on a specific container, add -e URNETWORK_METRICS=0.
See also the Configuration reference for the full list of telemetry variables.
As of v3.23.0-fix.30.8, the provider image is importable into the Pelican game-server panel as a one-click egg. The egg ships with the audit-preferred defaults pinned — vnStat off, IP checker off, and runtime self-update disabled.
- Download the egg JSON from the repo:
pelican/egg-urnetwork-323fix.json - In Pelican admin, go to Nests, select or create a nest, and use Import Egg to upload the JSON.
- The egg pulls
ghcr.io/full-bars/meso-miner:latest(multi-arch amd64/arm64).
| Variable | Editable | Description |
|---|---|---|
BUILD |
Yes |
stable, nightly, or jwt
|
USER_AUTH |
Yes | Email/phone for password-based auth. Required for stable/nightly, ignored for jwt
|
PASSWORD |
Admin-only | Password for password-based auth. Required for stable/nightly
|
AUTHCODE |
Admin-only | One-time auth code from ur.io. Required for jwt
|
PELICAN |
Hidden | Always yes — set by the egg, not editable |
ENABLE_VNSTAT |
Hidden | Always false in the egg |
ENABLE_IP_CHECKER |
Hidden | Always false in the egg |
-
BUILD=jwt: UsesAUTHCODE(one-time auth code from ur.io).pelican_panel.shcallsauth-provide "$AUTHCODE" -fonce; it does not retry on failure. -
BUILD=stableorBUILD=nightly: UsesUSER_AUTH+PASSWORDfor password-based authentication, with retry-on-failure and automatic JWT re-auth after 3 crashes.
Note
Under Pelican, BUILD=nightly currently behaves identically to BUILD=stable: the panel always routes through pelican_panel.sh, which runs the stable provider binary regardless of BUILD. The nightly-vs-stable binary split only applies outside Pelican mode. Don't rely on BUILD=nightly to get nightly binary behavior when PELICAN=yes.
The provider needs raw packet access on the node running the egg:
- Container capability
NET_ADMIN(and typicallyNET_RAW) - IP forwarding enabled on the host
- Outbound UDP allowed, for WebRTC P2P transport
No inbound ports need to be forwarded — the provider dials out.
Under Pelican (PELICAN=yes), the self-update checks in start_nightly.sh and urnet-tools update are disabled. The published image is the single source of truth; to update, re-pull the image via the panel (Settings → Reinstall) or docker pull.
The egg ships with automated CI checks (docker/scripts/test_pelican_gates.sh for egg JSON structure, variable contracts, and PELICAN-gate behavior; docker/scripts/test_pelican_smoke.sh for a full boot smoke test against a fake provider binary). For a real-panel import walkthrough and log output to expect, see pelican/README.md.