feat(pilot): make Docker Compose the canonical pilot enrollment payload (#1121)

* feat(pilot): make Docker Compose the canonical pilot enrollment payload

The Add Node dialog for a pilot-agent now returns a Compose snippet
instead of a single-line docker run command, and the enrollment dialog
walks the operator through a save-and-up flow. The compose project name
and container name align with what SelfUpdateService looks up at boot,
so a Compose-deployed pilot can be updated remotely through the Fleet
view without intervention on the remote host.

Docs (pilot-agent, remote-updates) were rewritten to match.

* test(e2e): align pilot enrollment spec with Compose payload

The spec was written against the docker-run payload; it now asserts the
Compose YAML the dialog renders.
This commit is contained in:
Anso
2026-05-20 02:05:21 -04:00
committed by GitHub
parent 0117556bea
commit 3ad6ea9c5d
6 changed files with 190 additions and 74 deletions
+53 -25
View File
@@ -23,6 +23,8 @@ Pick **Distributed API Proxy** (documented in [Multi-Node Management](/features/
The agent runs inside a second container on the remote host, using the same `saelix/sencho:latest` image. Setting `SENCHO_MODE=pilot` plus a primary URL and a one-time enrollment token puts the container into agent mode. On boot it dials the primary at `wss://<primary>/api/pilot/tunnel` and holds that connection open. For every tunneled request the primary demultiplexes frames to an internal loopback server, which re-issues the request locally on the agent host against its Docker socket. License tier, role checks, and all other authorization continue to flow from the primary, exactly like proxy mode.
Deploying with Docker Compose also lets the primary trigger over-the-air updates of the agent itself from the Fleet view, since Compose-managed containers carry the labels Sencho needs to recreate them in place.
Only outbound HTTPS from the remote to the primary is required. Nothing else is exposed.
## Enrollment walkthrough
@@ -36,29 +38,46 @@ On your primary instance open **Settings → Nodes** and click **Add Node**.
- **Name:** any label, for example `homelab-nuc`
- **Compose Directory:** the folder on the remote host where stack folders will live
Click **Add Node**. The form is replaced by an enrollment dialog with a one-line `docker run` command.
Click **Add Node**. The form is replaced by an enrollment dialog with a Docker Compose snippet.
<Frame>
<img src="/images/pilot-agent/enrollment-dialog.png" alt="Pilot Agent enrollment dialog with docker run command" />
<img src="/images/pilot-agent/enrollment-dialog.png" alt="Pilot Agent enrollment dialog with compose file" />
</Frame>
### 2. Run the command on the remote host
### 2. Deploy the agent on the remote host
Copy the command and paste it on the remote host. It looks like:
Copy the compose file and save it as `compose.yaml` on the remote host. It looks like:
```yaml
name: sencho-agent
services:
agent:
image: saelix/sencho:latest
container_name: sencho-agent
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- sencho-agent-data:/app/data
- /opt/docker/sencho:/app/compose
environment:
SENCHO_MODE: pilot
SENCHO_PRIMARY_URL: https://sencho.example.com
SENCHO_ENROLL_TOKEN: <short-lived token>
volumes:
sencho-agent-data:
```
Bring the agent up from the same directory:
```bash
docker run -d --restart=unless-stopped --name sencho-agent \
-v /var/run/docker.sock:/var/run/docker.sock \
-v sencho-agent-data:/app/data \
-v /opt/docker/sencho:/app/compose \
-e SENCHO_MODE=pilot \
-e SENCHO_PRIMARY_URL=https://sencho.example.com \
-e SENCHO_ENROLL_TOKEN=<short-lived token> \
saelix/sencho:latest
docker compose -f compose.yaml up -d
```
The enrollment token is single-use and expires after 15 minutes. On first connect the agent exchanges it for a long-lived tunnel credential, persisted inside the container volume at `/app/data/pilot.jwt`. Subsequent restarts reconnect automatically without needing a new token.
Deploying the agent through Docker Compose also lets the primary push remote updates to it from the Fleet view ([Remote Updates](/features/remote-updates)) without manual intervention on the remote host.
### 3. Confirm the node is online
The node flips to **Online** in the primary within a few seconds of the agent container starting. The **Endpoint** column shows `tunnel` alongside the time the primary last saw a frame from the agent.
@@ -77,19 +96,28 @@ If the agent container is destroyed before its first successful connect, or the
## Self-signed primary TLS certs
If your primary terminates TLS with an internal CA and the agent cannot validate the certificate against the system trust store, point the agent at the CA bundle with `SENCHO_PILOT_CA_FILE`. Mount the bundle into the agent container and add the env var:
If your primary terminates TLS with an internal CA and the agent cannot validate the certificate against the system trust store, point the agent at the CA bundle with `SENCHO_PILOT_CA_FILE`. Add the bundle mount and the env var to the compose file:
```bash
docker run -d --restart=unless-stopped --name sencho-agent \
-v /var/run/docker.sock:/var/run/docker.sock \
-v sencho-agent-data:/app/data \
-v /opt/docker/sencho:/app/compose \
-v /etc/ssl/internal-ca.pem:/etc/ssl/internal-ca.pem:ro \
-e SENCHO_MODE=pilot \
-e SENCHO_PRIMARY_URL=https://sencho.internal.example.com \
-e SENCHO_ENROLL_TOKEN=<short-lived token> \
-e SENCHO_PILOT_CA_FILE=/etc/ssl/internal-ca.pem \
saelix/sencho:latest
```yaml
name: sencho-agent
services:
agent:
image: saelix/sencho:latest
container_name: sencho-agent
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- sencho-agent-data:/app/data
- /opt/docker/sencho:/app/compose
- /etc/ssl/internal-ca.pem:/etc/ssl/internal-ca.pem:ro
environment:
SENCHO_MODE: pilot
SENCHO_PRIMARY_URL: https://sencho.internal.example.com
SENCHO_ENROLL_TOKEN: <short-lived token>
SENCHO_PILOT_CA_FILE: /etc/ssl/internal-ca.pem
volumes:
sencho-agent-data:
```
The agent uses the bundle as the only trust anchor for the tunnel WebSocket. TLS verification stays on; there is no env var to disable verification globally because that would defeat the credential trust model.
@@ -119,7 +147,7 @@ Each tunnel has fixed protocol-level ceilings to keep one misbehaving agent from
</Accordion>
<Accordion title="I ran the enrollment command on the wrong host">
Stop and remove the agent container on the wrong host (`docker stop sencho-agent && docker rm sencho-agent`), then regenerate the enrollment token on the primary and run the command on the correct host.
Tear the agent down on the wrong host (`docker compose -f compose.yaml down -v` from the directory you saved the file in), then regenerate the enrollment token on the primary and deploy the new compose file on the correct host.
</Accordion>
<Accordion title="Primary was restored from backup and the agent will not reconnect">
+2 -2
View File
@@ -13,11 +13,11 @@ Sencho can update remote nodes directly from the dashboard. When a node is runni
Remote updates work when each node meets these conditions:
- Deployed via **Docker Compose** (the recommended method)
- Deployed via **Docker Compose** (the canonical install for both primary instances and pilot agents)
- The Docker socket (`/var/run/docker.sock`) is mounted into the container
- Running a Sencho version that supports the `self-update` [capability](/features/node-compatibility)
Nodes deployed with `docker run` or orchestrators like Kubernetes do not support self-update. These nodes show an error message when you attempt to update them.
Nodes deployed by orchestrators like Kubernetes do not support self-update and need to be updated through the orchestrator's own deployment flow.
## Checking for updates