Run a local process as a member of your cluster: it resolves cluster service names, reaches cluster services, and is itself reachable in the cluster under a name — with no code change and no proxy settings in your app.
plug -s my-app:8080:3000 npm run start:dev
# reaches cluster services by name — and is itself reachable in the cluster
# as my-app:8080, forwarded to its local :3000Prefix any command with plug and it joins the cluster by name — Node, the
JVM, Python, Go, curl, gRPC, database drivers, anything. Stop the command and
your machine is exactly as it was.
- Reach cluster services by their real names, from your laptop — no port-forwards
to wire up, no
localhost:PORTmappings, no/etc/hostsedits. - Be reachable in the cluster under your own name — workloads call
my-app:8080and land on your local process, for the life of the session. - Works with any language or tool, unchanged — your app's sockets are never touched.
- Runs on Linux, macOS and Windows.
- Several clusters at once, side by side.
- Set up once per cluster, then no sudo or admin for daily use.
Two pieces: a small agent in the cluster, and the plug CLI on each dev machine.
In the cluster — add the agent to the stack you want to reach:
services:
plug:
image: docker.io/softwarity/plug:latest
ports: ["2222:22"]
# required — the agent creates your -s name through it (see below)
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# Swarm only, for -s: the signpost is a service, so run the agent on a
# manager (any single-node swarm node IS a manager) as a single replica.
# Ignored by plain Compose.
deploy:
replicas: 1
placement:
constraints: [node.role == manager]The socket line is required on Docker, Compose and Swarm: it is how the
agent creates your -s name. It is root on the host, so mount it only on a
cluster you trust — the trust plug's no-auth transport already assumes.
Kubernetes needs no socket: the bundled manifest grants a Services-only RBAC
role instead — see below.
Standalone agent, or Kubernetes: see the documentation.
On your machine — install straight from the cluster, in one line.
Linux and macOS:
ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null get@<cluster-host> install | shWindows, from Git Bash:
cluster=<cluster-host>
ssh -n -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null get@$cluster install-windows \
| bash -s -- $cluster 2222The install prepares your machine once — it may ask for your password (or, on
Windows, to run as Administrator) a single time — so that every later plug run
needs no privilege. After that you are ready.
plug -s my-app:8080:3000 npm run start:dev
plug -s my-api:8080:8080 ./mvnw spring-boot:run-s name:cluster-port:local-port is the name your process answers to in the
cluster; the same session reaches cluster services by name in return. It is
required: in a cluster a running process is a service, and a service has a
name — so name yours, even when nothing calls it back yet. The first run asks
which cluster to use and remembers it. Reaching another cluster is just naming it:
plug -p staging -s my-app:8080:3000 npm run start:dev # asks once, then rememberedEveryday commands: plug ls (list clusters), plug test (check one is
reachable), plug rn / plug rm (rename / remove), plug uninstall,
plug about.
Run the same process against two clusters in parallel — each stays isolated:
plug -p prod -s my-app:8080:3000 npm run start
plug -p staging -s my-app:8080:3000 npm run startSupported on all three OSes — proven simultaneously in CI on Linux, macOS and Windows. See the coverage matrix for the details.
-s name:cluster-port:local-port publishes name in the cluster and forwards
name:cluster-port to your machine's local-port, for the lifetime of the
session — no name pre-declared, no redeploy. Any workload calling
http://name:cluster-port lands on your process. The agent creates the name on
the fly, which it does per engine:
- Docker / Compose — mount the Docker socket on the agent (required). Each
-sspins up a tiny signpost container carrying the DNS alias, removed with the session. - Swarm — same socket; the signpost is a Swarm service, which joins the
stack's overlay whether or not it is
attachable, so no network change. The agent just needs to run on a manager node (to create services). - Kubernetes — no socket: the bundled manifest grants
a Services-only RBAC role, so
-screates and deletes the backing Service itself. - No socket — the agent falls back to static: you pre-declare the name yourself (a network alias, a Service). It works, but you lose the on-the-fly provisioning — so mount the socket.
services:
plug:
image: docker.io/softwarity/plug:latest
ports: ["2222:22"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock # required: the agent creates your -s nameThe Docker socket is root on the host — mount it only on a cluster you trust (the same trust plug's no-auth transport already assumes). plug verifies the full path at startup so a missing name fails loud, and the port closes with the session.
It carries TCP reached by name. UDP, QUIC and ping are not tunnelled (most clients fall back to TCP), and a hard-coded IPv6 literal is not either — a service reached by name is always fine.
There is deliberately no authentication: anyone who can reach the agent's port gets network access to the cluster it is attached to. Deploy the agent only on clusters you trust, and never expose its port on an untrusted network. The full model is in the documentation.
Everything else — how it works, deployment on Swarm and Kubernetes, profiles and versions, the security model, and the per-OS coverage matrix:
https://softwarity.github.io/plug/
Build from source with go build -o plug ./cli. Distribution is from the cluster
only — the agent image is the single source of the CLI, so there is no separate
package to install or keep in sync.