# 🐳 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. ## 🛠️ Managing Containers with `urnet-docker` > [!IMPORTANT] > **Host-Side vs. In-Container Tooling:** > - **`urnet-docker`** runs **on the Docker host** (outside the containers). It discovers provider containers, reads their in-container JWTs, and dispatches management tasks directly. > - **`urnet-tools`** runs **inside the container** (accessible via `docker exec -it urnet-tools `). Install `urnet-docker` once on the host (SHA-256 verified against the release API): ```sh 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: ```sh 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 tool ``` Common host-side commands: ```sh 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). ## 🗄️ Image Registries Primary image: ```text ghcr.io/full-bars/meso-miner:latest ``` ## 🔑 Persistent JWT 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. ## 🏃 Docker Run - GHCR The examples below use `urfix` as the container name. #### With proxy benchmarking: ```bash 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](Proxy-URL-Sources.md)) | | `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. ### 🔐 JWT Auth ```bash 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_HERE ``` Replace `AUTH_CODE_HERE` with your token from [ur.io](https://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: ```bash -e URNETWORK_AUTH_CODE=YOUR_CODE ``` To label this server in webhook alerts and health logs, add: ```bash -e URNETWORK_NODE_NAME=your-server-name ``` ### ✉️ Email/Password Auth ```bash 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=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:latest ``` ## 🏃 Docker Run - Docker Hub The commands are identical to GHCR except for the image name. ### 🔐 JWT Auth ```bash 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_HERE ``` Alternative method: ```bash -e URNETWORK_AUTH_CODE=YOUR_CODE ``` ### ✉️ Email/Password Auth ```bash 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=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:latest ``` ## 🐙 Docker Compose For 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](Multi-Container-Scaling.md) guide. ### 🔐 JWT Auth ```yaml 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: 1. Trailing argument: `docker compose run --rm urnetwork AUTH_CODE_HERE` 2. Environment variable: add `URNETWORK_AUTH_CODE=AUTH_CODE_HERE` to the `environment:` section, then run `docker compose up -d` After the JWT is saved to the volume, subsequent starts need no auth code: ```bash docker compose up -d ``` ### ✉️ Email/Password Auth ```yaml 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=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: ``` ## 💾 RAM Logging 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: > ```bash > docker exec -it tail -f /dev/shm/urnetwork.log > ``` > [!NOTE] > `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: ```bash 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_CODE ``` View logs live: ```bash docker exec -it urfix tail -f /dev/shm/urnetwork.log ``` RAM logs are capped at 1MB with automatic rotation and are lost when the container restarts. ## 📊 Proxy Summary View a single-pane fleet overview showing proxy counts by source (file vs URL), health breakdown, and URL cache status: ```sh docker exec -it provider proxy summary ``` ## 📡 Report URL (Deprecated) > [!WARNING] > **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: ```sh # Set report URL docker exec -it sh -c 'echo "http://HUB_IP:8080" > "$HOME/.urnetwork/report_url"' # Check current URL docker exec -it sh -c 'cat "$HOME/.urnetwork/report_url" 2>/dev/null || echo "not set"' # Disable docker exec -it 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: ```sh 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.) ## ♻️ Hot-Restart 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:** ```bash 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_CODE ``` **Status check:** ```bash 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. ## 💾 Session Save/Load 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): ```bash 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): ```bash docker cp atlanta.urnsession urfix:/root/.urnetwork/ docker exec -it urfix urnet-tools session load /root/.urnetwork/atlanta.urnsession ``` The 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. ## 🩺 Viewing Proxy Health 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 proxy-health` - Live-tail RAMLOGS on: `docker exec -it sh -c "tail -f /dev/shm/urnetwork.log | grep -E '\[health\]\[proxies\]|\[pulse\]'"` - Live-tail RAMLOGS off: `docker logs -f 2>&1 | grep -E '\[health\]\[proxies\]|\[pulse\]'` ## 🏎️ Auto-Tune Performance 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: ```bash 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_CODE ``` > [!NOTE] > 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. ## 🔄 Automatic Updates [Watchtower](https://containrrr.dev/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: ```yaml watchtower: image: containrrr/watchtower container_name: watchtower restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock command: --cleanup --interval 3600 urfix ``` > [!IMPORTANT] > 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](#-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. ## ⏳ Idle Update 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: ```sh docker exec -it urnet-tools idle-update ``` It 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 ` | `5120` (5 KiB/s) | Traffic at or below this is considered "quiet". | | `--window ` | `300` (5 min) | How long traffic must stay quiet before updating. | ```sh # Wait for a 10-minute quiet window under 10 KiB/s docker exec -it urnet-tools idle-update --window 600 --threshold 10240 # Skip waiting entirely and update immediately docker exec -it urnet-tools idle-update --window 0 ``` > [!NOTE] > 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. ## 📊 (Optional) Running with vnStat Bandwidth Monitor 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: ### 1. Configuration Requirements - Set `-e ENABLE_VNSTAT=true` - Map a host port to container port `8080`: `-p :8080` - Mount a persistent volume to preserve traffic history across restarts: `-v _vnstat:/var/lib/vnstat` ### 2. Single-Container Example ```bash 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_HERE ``` Access 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). --- ### 3. Multi-Container Rules: Offsetting Ports & Volumes 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`). #### Multi-Container Compose Snippet: ```yaml 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: ``` --- ### 📊 Prometheus + Grafana Monitoring Bundle (v3.23.0-fix.31.2) 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`: ```bash # Start the provider + monitoring stack together docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d ``` This brings up: - **Prometheus** — configured to scrape `http://urnetwork:9091/metrics` at 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](Configuration.md#-monitoring--telemetry) reference for the full list of telemetry variables. ## 🐦 Pelican Panel As of `v3.23.0-fix.30.8`, the provider image is importable into the [Pelican game-server panel](https://github.com/pelican-dev/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. ### Importing the egg 1. Download the egg JSON from the repo: `pelican/egg-urnetwork-323fix.json` 2. In Pelican admin, go to **Nests**, select or create a nest, and use **Import Egg** to upload the JSON. 3. The egg pulls `ghcr.io/full-bars/meso-miner:latest` (multi-arch amd64/arm64). ### Configuration variables | 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](https://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 | ### Authentication modes - **`BUILD=jwt`**: Uses `AUTHCODE` (one-time auth code from [ur.io](https://ur.io)). `pelican_panel.sh` calls `auth-provide "$AUTHCODE" -f` once; it does not retry on failure. - **`BUILD=stable`** or **`BUILD=nightly`**: Uses `USER_AUTH` + `PASSWORD` for 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`. ### Resource requirements The provider needs raw packet access on the node running the egg: - Container capability `NET_ADMIN` (and typically `NET_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. ### Update behavior 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`. ### Testing 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`. ## Letting the Thrash Watchdog Restart the Container's Provider The swap-thrash watchdog ([details](Configuration.md#swap-thrash-watchdog)) restarts the provider by exiting with status 75. Inside a container that is allowed only when you opt in, because only you know that the container restarts the provider afterwards: ```yaml services: provider: image: ghcr.io/full-bars/meso-miner:latest restart: unless-stopped environment: URNETWORK_SELF_HEAL: "1" URNETWORK_EXIT75_OK: "1" volumes: - ur_config_1:/root/.urnetwork # the thrash cap and its 24h ceiling live here ``` The start scripts (`start_stable.sh`, `start_nightly.sh`, `start_jwt.sh` and the provider loop of the Pelican egg) treat exit 75 as a planned restart: they wait 5 seconds, restart the provider and leave the JWT alone. Without `URNETWORK_EXIT75_OK=1` the watchdog logs `no-supervisor` and never exits. Without a writable, persistent `~/.urnetwork` it logs `persist-failed` and never exits. On runtimes that do not create `/.dockerenv` (Podman, Kubernetes) also set `URNETWORK_CONTAINER=1`.