NodeShare lets two computers — anywhere in the world — connect directly and securely, so one machine can run a piece of work (a "task") on the other.
You start NodeShare on a machine that has spare power (the Provider), share a single command with a friend, and they connect from their machine (the Requester). From there you get an interactive terminal where you can:
- check the connection (
ping), - inspect the other machine's hardware (
sysinfo), - send a folder of code to the Provider (
create-task), and - run that code remotely inside Docker and watch the output live (
run-task).
No port forwarding, no accounts, no cloud. It works across different home networks out of the box.
- What NodeShare does (in plain English)
- Key words you'll see
- Prerequisites
- Install / build
- Quick start: your first remote task in ~5 minutes
- The interactive terminal — every command explained
- Writing your own task
- Command-line reference
- Other ways to connect (LAN, self-hosted relay)
- What happens under the hood
- Security model
- Cleanup & reliability
- Troubleshooting
- Project structure
- Protocol stack
- License
Imagine you have a powerful desktop at home, and a friend on the other side of the country has a slow laptop. Your friend has some Python code they want to run, but their laptop can't handle it. With NodeShare:
- You run
nodeshare starton your desktop. NodeShare prints one command. - You send that command to your friend (Discord, WhatsApp, email — anything).
- Your friend pastes the command into their terminal. They're now connected directly to your desktop, over an encrypted link.
- Your friend types
create-task ./their-codeto send the code to your desktop, thenrun-taskto run it. The output streams back to their screen in real time, even though it's actually running on your machine.
There are always two roles:
| Role | Command | What it does |
|---|---|---|
| Provider | nodeshare start |
Offers its machine. Receives tasks and runs them in Docker. |
| Requester | nodeshare connect |
Connects to a Provider and drives it through the interactive terminal. |
The Provider is the one doing the heavy lifting. The Requester is the one asking for work to be done.
You don't need to memorise these, but they'll make everything else clearer.
- Peer — any machine running NodeShare.
- Peer ID — a long unique fingerprint for a machine, like
12D3KooWSixUHPAtCHVnbi517roFKaHZceVL4FJdAez5rDcGEadq. It's generated once on first run and stays the same forever after, so a peer is always recognisable. - Multiaddress (or "multiaddr") — a full "postal address" for a peer that
bundles how to reach it and who it is. Example:
/dns4/abc.pinggy.link/tcp/33843/p2p/12D3KooW.... You don't write these by hand — NodeShare prints a ready-to-use one for you. - Tunnel (Pinggy) — a free service NodeShare uses to give your machine a public address automatically, so peers on other networks can reach you without any router configuration. NodeShare launches it for you.
- Task — a folder of code you want to run remotely. It must contain a
Makefileand aDockerfile; the actual program can be in any language (Python, C++, Go, …) — theDockerfileinstalls the toolchain and theMakefilebuilds/runs it (see section 7). - Interactive terminal (REPL) — after you connect, you get a
nodeshare>prompt where you type commands likeping,sysinfo,create-task, andrun-task.
| You need… | On which machine | Why | Notes |
|---|---|---|---|
| Go 1.21+ | Both (to build) | NodeShare is written in Go. (Developed against Go 1.25.) | Download Go. Skip if you use a prebuilt binary. |
| Docker | Provider only | run-task builds and runs your task inside a container. |
Get Docker. The Docker daemon must be running. |
ssh |
Both (for the tunnel) | NodeShare uses it to open the free Pinggy tunnel. | Pre-installed on Linux, macOS, and Windows 10+. |
| Internet | Both | To reach each other across networks. | A LAN-only mode exists too (see section 9). |
Only the Provider needs Docker. The Requester just sends a folder and reads logs — it never runs anything locally.
First, get the source and move into the project folder:
git clone <this-repo-url>
cd NodeSharemake build
# produces ./dist/nodesharemake is usually not available on Windows, so build directly with Go:
go build -o dist/nodeshare.exe ./cmd/nodeshare
# produces .\dist\nodeshare.exe# Linux / macOS
./dist/nodeshare info
# Windows
.\dist\nodeshare.exe infoYou should see your machine's Peer ID and hardware details.
Naming in this guide: examples below write
nodesharefor brevity. Substitute./dist/nodeshare(Linux/macOS) or.\dist\nodeshare.exe(Windows).
This walkthrough uses the bundled example task in examples/hello-task/, which
simply prints "Hello World". You can run both sides on the same computer
(two terminal windows) to try it out, or on two different computers.
On the machine that will do the work, run:
nodeshare startNodeShare opens a free tunnel and, after ~15 seconds, prints a banner like this:
==================================================
NodeShare Ready
==================================================
Public Address:
/dns4/pieww-106-200-16-7.run.pinggy-free.link/tcp/33843
Peer ID:
12D3KooWSixUHPAtCHVnbi517roFKaHZceVL4FJdAez5rDcGEadq
Share ONE of the following commands with your peer:
Linux / macOS:
./dist/nodeshare connect /dns4/pieww-106-200-16-7.run.pinggy-free.link/tcp/33843/p2p/12D3KooWSixUHPAtCHVnbi517roFKaHZceVL4FJdAez5rDcGEadq
Windows PowerShell:
.\dist\nodeshare.exe connect /dns4/pieww-106-200-16-7.run.pinggy-free.link/tcp/33843/p2p/12D3KooWSixUHPAtCHVnbi517roFKaHZceVL4FJdAez5rDcGEadq
==================================================
Type a command, or 'help'. (Ctrl-C to stop)
nodeshare>
The Provider now sits at its own nodeshare> prompt, waiting. Leave it running.
Copy the line under "Linux / macOS" or "Windows PowerShell" (whichever your peer uses) and send it to them. It already contains the full address and Peer ID — nothing to edit.
Trying it on one computer? Just open a second terminal and use the command yourself.
In the other terminal (or on your friend's machine), paste the command:
nodeshare connect /dns4/pieww-106-200-16-7.run.pinggy-free.link/tcp/33843/p2p/12D3KooWSixUHPAtCHVnbi517roFKaHZceVL4FJdAez5rDcGEadqYou'll see a summary of the Provider, then an interactive prompt:
── Connected Peer ──────────────────────────────────────
Peer ID : 12D3KooWSixUHPAtCHVnbi517roFKaHZceVL4FJdAez5rDcGEadq
Role : worker
Latency : 212.518ms
Hostname : fedora
OS / Arch : linux / amd64
CPU : Intel(R) Core(TM) Ultra 7 155H (16p / 22l)
RAM : 15.10 GB total
Disk : 197.91 GB total
Interactive terminal ready. Type 'help' for commands.
nodeshare(…cGEadq)>
At the nodeshare(...)> prompt, type:
ping
→ shows the round-trip time to the Provider.
sysinfo
→ asks the Provider for its live hardware details and prints them.
help
→ lists every available command.
Send the example folder to the Provider:
create-task examples/hello-task
The path is on the Requester's machine — it's the folder being sent.
Output:
Sending task folder "examples/hello-task" …
✓ received 3 file(s) into /tmp/nodeshare/tasks/task-20260607-083028-b6c8cd4b
task id: task-20260607-083028-b6c8cd4b (run it with 'run-task')
The three files (Makefile, Dockerfile, main.py) are now on the Provider.
Nothing has executed yet — this step only transfers files.
run-task
The Provider builds a Docker image and runs it. The build and program output
stream back to your screen live, each line prefixed with [REMOTE]:
Running task task-20260607-083028-b6c8cd4b on the remote peer …
[REMOTE] Building image nodeshare-task-20260607-083028-b6c8cd4b …
[REMOTE] Step 1/5 : FROM python:3.12-slim
[REMOTE] ...
[REMOTE] Successfully tagged nodeshare-task-20260607-083028-b6c8cd4b:latest
[REMOTE] Running container (make) …
[REMOTE] python main.py
[REMOTE] Hello World from a NodeShare remote task!
✓ Task completed successfully
You just ran code on another machine and watched it happen in real time.
Type quit (or exit) on the Requester to disconnect. On the Provider, type
quit or press Ctrl-C to stop. When the Requester leaves, the Provider
automatically deletes the task files and the Docker image it built — no leftovers.
After connecting, both sides have a prompt. They have different command sets because they play different roles.
| Command | What it does |
|---|---|
ping |
Measures and prints the round-trip latency to the Provider. |
sysinfo |
Requests the Provider's current hardware/OS details and prints them. |
create-task <dir> |
Validates the folder <dir> and transfers all its files to the Provider. No execution. |
run-task [id] |
Tells the Provider to build & run a transferred task in Docker, streaming logs back live. With no id, it runs the most recently created task. |
help (or ?) |
Lists these commands. |
clear |
Clears the screen. |
quit / exit |
Closes the connection and exits. |
Tip: press Tab to auto-complete command names.
The Provider mostly serves the Requester, but it has a few local commands:
| Command | What it does |
|---|---|
peers |
Lists peers currently connected to this Provider. |
info / sysinfo |
Shows this machine's own system information. |
help (or ?) |
Lists these commands. |
clear |
Clears the screen. |
quit / exit |
Stops the daemon and exits. |
While the Provider sits at its prompt, it also prints a box whenever a peer connects or a task is received, so you can see activity at a glance.
A task is just a folder. To be valid it must contain:
| File | Purpose |
|---|---|
Dockerfile |
Describes the container image your code runs in (base image, dependencies, files). |
Makefile |
Defines what make runs. The Provider runs make inside the container. |
If either is missing,
create-taskrefuses to send the folder and tells you what's wrong. Everything else is up to you: your program can be in any language, and you can include as many source files, data files, etc. as you like — the whole folder is transferred. NodeShare never inspects your source; it only runsdocker buildthenmake.
When you run run-task, the Provider does exactly two things:
docker build -t <image> <your-task-folder> # 1. build the image
docker run --rm <image> make # 2. run `make` inside itSo your Dockerfile must produce an image where running make works — that
means make and Python must be installed, and your files copied in.
main.py
print("Hello World from a NodeShare remote task!")Makefile (the indented line must start with a real Tab, not spaces)
.PHONY: run
run:
python main.pyDockerfile
FROM python:3.12-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends make \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . /app
# Default command runs the Makefile's first target.
CMD ["make"]Walking through it:
FROM python:3.12-slim— start from a small image that already has Python.RUN apt-get … make— installmake(the slim image doesn't ship it).WORKDIR /app+COPY . /app— put your task files inside the image.makeruns therun:target →python main.py→ prints the message.
To make your own task, copy this folder, swap main.py for your code, and adjust
the Makefile/Dockerfile (e.g. pip install your dependencies).
Nothing about NodeShare is Python-specific — it only runs docker build then
make. To run C++ instead, the folder just needs a Dockerfile with a C++
toolchain and a Makefile that compiles and runs your program:
main.cpp
#include <iostream>
int main() { std::cout << "Hello World from a C++ NodeShare task!\n"; }Makefile (recipe lines indented with a real Tab)
.PHONY: run
run:
g++ -O2 -o app main.cpp
./appDockerfile (the official gcc image already ships g++ and make)
FROM gcc:13
WORKDIR /app
COPY . /app
CMD ["make"]The same pattern works for Go, Rust, Node.js, etc. — pick a base image with the
right toolchain and have the Makefile build and run your program.
NodeShare is a Cobra-based CLI. These are the top-level commands you run from
your shell. (create-task/run-task/ping/sysinfo inside the interactive
terminal are covered in section 6.)
| Command | Description |
|---|---|
nodeshare start |
Start the Provider. Opens a tunnel, prints a connect command, opens a prompt. |
nodeshare connect <addr> |
Connect to a Provider and open the interactive terminal. |
nodeshare peers |
List peers currently connected to this machine. |
nodeshare ping <peerID> |
Send 5 pings to a connected peer and report RTT statistics. |
nodeshare info |
Print this machine's Peer ID, system info, and listen addresses. |
nodeshare relay |
Run a standalone relay node others can bounce through (advanced). |
| Flag | Used with | Meaning |
|---|---|---|
--port |
all | TCP/QUIC listen port (default 4001; connect uses a random free port). |
--role |
start, connect | A label for this node, e.g. worker (default) or coordinator. |
--log-level |
all | Log verbosity: debug, info (default), warn, error. |
--disable-pinggy |
start | Don't open the auto tunnel; show local LAN addresses instead. |
--announce-addr |
start, relay | Extra address to advertise (e.g. a manual port-forward). Repeatable. |
--no-relay |
start | Disable relay/hole-punching (local-only mode). |
--bootstrap |
start | Use a custom relay/bootstrap peer instead of the public defaults. |
If both machines are on the same Wi‑Fi/LAN, skip the tunnel for speed:
# Provider:
nodeshare start --disable-pinggy
# It prints local addresses like /ip4/192.168.1.25/tcp/4001/p2p/<PEER_ID>
# Requester:
nodeshare connect /ip4/192.168.1.25/tcp/4001/p2p/<PEER_ID>If you have a machine with a public IP (a VPS, or a home machine with port 4001 forwarded), you can run your own relay instead of the public defaults:
# On the public machine:
nodeshare relay
# Prints: /ip4/203.0.113.10/tcp/4001/p2p/<RELAY_PEER_ID>
# Both peers then start with --bootstrap:
nodeshare start --bootstrap /ip4/203.0.113.10/tcp/4001/p2p/<RELAY_PEER_ID> Requester Provider
(nodeshare connect) (nodeshare start)
│ │
│ 1. dial + Noise-encrypted handshake │
│─────────────────────────────────────────▶│
│ 2. exchange system info + ping/pong │
│◀────────────────────────────────────────▶│
│ │
│ ===== interactive terminal opens ===== │
│ │
│ create-task: manifest + file chunks ──▶ │ saves to
│ ◀── "received" ack │ /tmp/nodeshare/tasks/<id>/
│ │
│ run-task: "run-task <id>" ────────▶ │ docker build …
│ ◀── [REMOTE] log lines ── │ docker run --rm … make
│ ◀── TaskCompletion ────── │ (streams every line live)
│ │
│ quit / disconnect ─────────────────────▶ │ cleans up files + image
- Transport & encryption: built on
go-libp2p. All traffic is encrypted (Noise) and multiplexed (Yamux) over TCP or QUIC. - NAT traversal: UPnP/NAT‑PMP, public Circuit Relay v2, and DCUtR hole punching let two home networks connect without port forwarding. The Pinggy tunnel is an additional zero-config path.
- Messages: every message is a length-prefixed Protobuf
Envelope(handshake, sysinfo, ping/pong, terminal commands, task transfer, file chunks, log updates, task completion).
NodeShare is designed for running tasks with people you trust. Be aware:
- All traffic is encrypted end-to-end (Noise). Peers are identified by a cryptographic Peer ID.
- File transfers are sandboxed to a task folder. The Provider rejects any
file path that tries to escape its task directory (no
../tricks) and any file not listed in the transfer manifest. - Tasks run inside Docker, isolated from the host, and are cleaned up after.
- Running a task executes the Requester's code on the Provider. Only accept tasks from people you trust — a Dockerfile can do anything Docker can do.
- The Provider's address is public while the tunnel is open. The Peer ID in the connect command acts as the shared secret; only share it with your peer.
NodeShare tries hard not to leave a mess on the Provider:
- When the Requester disconnects (clean
quitor a dropped connection), the Provider deletes the transferred task folders and removes the Docker images it built for that session. - Containers run with
docker run --rm, so they self-remove on exit. - If the Requester vanishes mid-run, a watchdog on the Provider notices the dropped connection and force-removes the running container, so no "zombie" container keeps consuming resources.
- A failed task is reported back as a clear status (e.g. "Docker build failed") rather than silently hanging — and the session stays alive so you can retry.
"[tunnel] could not be opened" / the public banner never appears (Provider).
The free Pinggy tunnel couldn't start within ~45 seconds — usually a firewall
blocking the SSH connection, ssh missing from PATH, or a temporary Pinggy
hiccup. This is an external-network issue, not a NodeShare bug. When it happens,
NodeShare automatically prints ready-to-paste nodeshare connect commands for
your local/LAN addresses, so peers on the same network can still connect
immediately. For peers on a different network you can:
- simply re-run
nodeshare startto retry the tunnel (it's often transient), - skip the tunnel entirely with
--disable-pinggy(LAN use), or - open the tunnel manually and advertise it:
ssh -4 -p 443 -R0:localhost:4001 tcp@a.pinggy.io, then start with--announce-addr /dns4/<host>/tcp/<port>.
"tunnel unavailable" on Windows. Enable the OpenSSH client: Settings → Optional features → OpenSSH Client (ships with Windows 10+).
run-task fails with "Docker build failed" or a Docker connection error.
Make sure Docker is installed and the Docker daemon/Docker Desktop is running
on the Provider. Test with docker run --rm hello-world.
create-task says my folder is invalid.
The folder must contain a Makefile and a Dockerfile (the program itself can
be any language). Check the names and that you pointed at the right folder.
make: *** missing separator inside the container.
Your Makefile recipe lines must be indented with a real Tab, not spaces.
Connections only work on the same network.
Confirm relay is enabled (it is by default — don't pass --no-relay) and that
both machines have internet access.
The tunnel expires after ~60 minutes.
The free Pinggy tunnel is time-limited. Re-run nodeshare start to get a fresh
address and a new connect command.
The interactive prompt looks garbled / doesn't accept typed commands. The interactive terminal needs a real terminal (TTY). Run NodeShare directly in a terminal window — not through a pipe or a non-interactive wrapper.
| Path | Responsibility |
|---|---|
cmd/nodeshare/ |
CLI entry point, Cobra commands, and the interactive REPLs. |
internal/config/ |
Runtime configuration, defaults, and the bootstrap relay list. |
internal/host/ |
libp2p host construction, NAT/relay options, and identity persistence. |
internal/tunnel/ |
Pinggy SSH tunnel lifecycle — launch, parse, and address injection. |
internal/node/ |
Connection lifecycle, interactive session, task transfer & execution. |
internal/protocol/ |
Wire framing, the Protobuf message dispatcher, and message handlers. |
internal/store/ |
In-memory, thread-safe peer registry. |
internal/sysinfo/ |
Hardware and OS telemetry collection. |
internal/task/ |
Task-folder validation, file listing, IDs, and safe path handling. |
proto/ |
Protobuf message definitions. |
gen/ |
Generated Go bindings (regenerated via make proto). |
examples/hello-task/ |
A ready-to-run Python example task that prints "Hello World". |
examples/hello-cpp/ |
The same example written in C++ (gcc image). |
examples/hello-go/ |
The same example written in Go (golang image). |
NodeShare speaks the standard libp2p protocol suite, so it interoperates cleanly across operating systems and network conditions.
| Layer | Technology |
|---|---|
| Transports | TCP, QUIC-v1 |
| Security | Noise |
| Multiplexing | Yamux |
| Identity | Persistent Ed25519 keypair |
| NAT traversal | UPnP / NAT-PMP, Circuit Relay v2, DCUtR |
| Messaging | Length-prefixed Protobuf Envelope |
make build # compile the binary into dist/
make test # run the unit test suite
make proto # regenerate Go bindings from proto/*.proto
make cross # build release binaries for all supported platforms
make help # list every targetRegenerating Protobuf bindings (
make proto) needsprotocandprotoc-gen-go. Pre-generated bindings are committed, so a normal build does not require them.
See the repository for license details.