Blog

Engineering

How a remote host joins without opening a port

The FleetOps agent dials out, runs yamux over one WebSocket and hands the server raw streams to the host's Docker socket. Here is why it is built that way.

The usual way to manage a remote Docker host from a web UI is to expose its Docker API on TCP, ideally with TLS client certificates. That puts a root-equivalent API on the network, and it needs an inbound port, which edge sites behind NAT often cannot give you.

FleetOps turns the connection around. The host runs fleetops-agent, and the agent connects to the server.

Joining

An admin adds the remote host as an environment and gets a one-time command with a join token. The agent presents the token on its first connection and receives its own long-lived key in exchange, which it stores locally. The join token is then spent. Rotating the key from the environment page gives a new command; the old key stops working.

One WebSocket, many streams

The agent opens one WebSocket to /api/v1/agent/connect and runs a yamux session over it. The first stream carries a small JSON control protocol: hello, welcome, ping and bye.

Every other stream is opened by the server and is a raw byte pipe to the host’s Docker socket. The agent does not understand Docker requests at all; it copies bytes. On the server, the Docker client library runs over a transport whose connector opens a new yamux stream instead of a TCP connection. The same code that talks to the local socket talks to the remote host, so container logs, terminals with hijacked connections, image builds and stats need nothing special.

Each stream has its own 256 KiB flow-control window. A browser that reads a log slowly stalls only its own stream, not the terminal someone else has open on the same host.

What this buys

  • No inbound port. The host only makes an outbound connection to the server, so it works behind NAT, carrier-grade NAT and most firewalls.
  • No exposed socket. The Docker socket stays a Unix socket on the host. Nothing on the network can reach it except through an authenticated FleetOps session, which goes through the same role check and audit log as a local environment.
  • One code path. Local and remote environments share the Docker implementation, so a feature that works locally works remotely the day it ships.

Kubernetes clusters use the same tunnel. The in-cluster agent, fleetops-agent --kube, forwards to the cluster’s API server, and the server never holds the cluster’s credentials. The install guide has the commands for both.