Files
sencho/docs/tutorials/enroll-a-remote-node.mdx
T
Anso d07cfc34d1 docs: v1 docs refresh (batch 10) (#2182)
* docs(tutorials): refresh the create and approve a Blueprint walkthrough

Walk the 0.98.0 Blueprint editor, rollout preview, and detail sheet end to end and update the steps to match: the empty-catalog button, the staged label tags, the GitOps authority and evidence block, the approval state shown in the sheet, the audit rows each action leaves, and the real port-conflict recovery. Permissions are described as the stack and node permissions they are, and all seven screenshots are retaken.

* docs(tutorials): refresh the connect a Git source walkthrough

Walk the 0.98.0 Git Source sheet from an unlinked stack to an applied commit and update the steps to match: the toolbar and Anatomy entry points, the Custom CA field, the four apply behaviors, the first-pull Add row in the change plan, the Accepted answer and stage row, and the exact ref error. Add the rule that a linked stack must be detached before its repository or ref changes. All six screenshots are retaken.

* docs(tutorials): refresh the catch and fix drift walkthrough

Walk a real image drift through detection, re-check, redeploy, and dismiss on 0.98.0 and update the text to match: the redeploy confirmation's consequence label, the health gate entry on the Activity timeline, and the Dismiss toast with Undo. Dismissing and restoring a finding is noted as needing the same stack permission as redeploying. All seven screenshots are retaken.

* docs(tutorials): refresh the move a Blueprint to a new node walkthrough

Walk a real migration between a retiring proxy node and the hub on 0.98.0 and update the tutorial to match: the Placement control tab, the toasts on pin and cordon, Cordon in the node details sheet, the Needs confirmation status block before the rollout is confirmed, the transient Completion unknown state after it, and the audit rows including the stack the apply creates. All eleven screenshots are retaken.

* docs(tutorials): refresh the missing required variable guardrail walkthrough

Walk the guardrail on 0.98.0 from a clean deploy to a refused Save & Deploy and Update and back to a healthy update, and update the steps to match: the toast and the Update failed chip that carry the message, the readiness dialog wording, the Environment tab's missing count, the Deploy Guardrails section as it reads now, and the activity entries. Adds the screenshots for the blocked Save & Deploy and the failure chip, and retakes the rest.

* docs(tutorials): refresh the deploy from CI webhook walkthrough

Run the webhook end to end on 0.98.0, including a signed trigger and the newline-signed request the troubleshooting section describes, and update the text to match: the Action list, including Git source sync with a pointer to the Webhooks page, the new form, callout, and Recent executions states, and the uptime and log wording on the redeployed stack. All five screenshots are retaken with the trigger URL and secret replaced by generic values.

* docs(tutorials): refresh the enroll a remote node walkthrough

Enroll a real Pilot agent on 0.98.0 and update the steps to match: the Pilot enrollment dialog and its Done button, the tunnel states after the agent starts, the switcher and Fleet cards beside the nodes already enrolled, the pencil that opens Edit node, and the hub-only view list, which now includes GitOps. The example compose directory is changed to a dedicated media path, and all six screenshots are retaken with host addresses, seal pins, and the token replaced by generic values.

* docs(tutorials): refresh the Custom OIDC single sign-on walkthrough

Run the whole flow on 0.98.0 against a real Keycloak 26 over a private CA and update the text to match: the login button is labelled with the Display Name rather than a fixed phrase, the Active badge and header counts after Save, the stored-secret state after Save, the Log Out path, what happens when the browser is already signed in at the identity provider, and the full list of fields to leave blank. All six screenshots are retaken with the issuer shown as a generic host.

* docs(tutorials): refresh the auto-patch with a label walkthrough

Run the label-driven update schedule twice on 0.98.0 and update the text to match: the Labels submenu states, the Current matches panel that lists every node, the run time and per-node details in the Execution history, the second "all images up to date" run, and the node's Updates chip as the proof the pending update cleared. The Update board example is replaced because other stacks on a real fleet keep it from ever being empty. All eight screenshots are retaken.
2026-10-23 18:29:04 -04:00

97 lines
8.1 KiB
Plaintext

---
title: Enroll a Remote Node
sidebarTitle: Enroll a remote node
description: Bring a second server under management in the same Sencho console, without opening any inbound ports on it.
---
Say you run Sencho on one server already, and you separately have a home media server running a Jellyfin stack via Docker Compose, on a connection with no port forwarding. This walks through bringing that second server into your Sencho console as a remote node, using **Pilot Agent** mode: the remote host only needs outbound HTTPS, so nothing has to be opened on your router or firewall.
By the end, both servers appear side by side in the same console, and Sencho can see the Jellyfin containers already running on the second one.
This tutorial does not cover **Distributed API Proxy** mode (for a remote host you already expose on a stable URL), [Sencho Mesh](/features/sencho-mesh) cross-node networking, or adopting a discovered Compose file into a managed stack. See the [Multi-Node Management](/features/multi-node) feature page for those.
## Prerequisites
- An **admin** account on the Sencho instance that will act as the control instance. Adding, editing, and testing nodes requires admin.
- Docker and the Docker Compose plugin installed on the second server, with SSH or console access as the user that owns the Docker socket.
- The second server must be able to reach your control instance over outbound HTTPS (or HTTP, if both are on the same private network or VPN).
<Warning>
If your control instance is not reachable from the public internet or the remote server's network (for example, it only listens on `localhost` with no port forwarding and no `SENCHO_PUBLIC_URL` set), the agent will never dial home and the node will sit at `tunnel (waiting)` indefinitely. Confirm the remote host can reach your control instance's URL before starting.
</Warning>
<Steps>
<Step title="Open the Add node form">
On your control instance, click your avatar in the top-right and choose **Settings**. In the sidebar, under **Infrastructure**, pick **Nodes**, then click **Add node**.
Set **Name** to something that identifies the host (`media-server` here). Leave **Type** as **Remote** and **Mode** as **Pilot Agent**, both defaults. In **Compose Directory**, enter the absolute path on the remote host where its Compose stacks already live, or where you want them to live, `/home/ubuntu/media-stacks` in this example. The agent mounts that same path inside its own container, so existing bind-mount paths in your Compose files keep working unchanged.
<Frame>
<img src="/images/tutorials/enroll-a-remote-node/add-node-filled.png" alt="Add remote node dialog with Name set to media-server, Type set to Remote, Mode set to Pilot Agent, and Compose Directory set to /home/ubuntu/media-stacks, with Cancel and Add node buttons." />
</Frame>
Click **Add node**.
</Step>
<Step title="Deploy the agent on the remote host">
An **Enroll the pilot agent** dialog opens with a generated Compose file and a start command.
<Frame>
<img src="/images/tutorials/enroll-a-remote-node/pilot-enroll-modal.png" alt="Enroll the pilot agent dialog under the Nodes, Pilot enrollment label. Step 1 shows a generated compose.yaml with SENCHO_MODE set to pilot, SENCHO_PRIMARY_URL pointing at the control instance, a SENCHO_ENROLL_TOKEN, and COMPOSE_DIR. Step 2 shows the docker compose up -d start command. The footer reads Expires 14m from now, with a Copy compose file button and a Done button." />
</Frame>
Save Step 1's contents as `compose.yaml` in a directory on the remote host (it does not need to be the Compose Directory itself). If the dialog also shows a hub CA block, save that PEM as `sencho-hub-ca.pem` in the same directory. Then run Step 2's command in that directory as the user that owns Docker:
```bash
docker compose up -d
```
This pulls the `saelix/sencho:latest` image, mounts the host's Docker socket, and starts a container named `sencho-agent` that dials out to your control instance. It does not touch or restart anything already running on the host, including your existing Jellyfin container.
The enrollment token embedded in the file is valid for **15 minutes and can only be used once**. If it expires before you run the command, see [If something goes wrong](#if-something-goes-wrong) below.
</Step>
<Step title="Verify the tunnel connects">
Select **Done**. The new row on the Nodes table starts at **Status: Unknown** with Endpoint `tunnel (waiting)`. Once the agent dials home, usually within a few seconds of `docker compose up -d` finishing, the row flips to **Status: Online** and the Endpoint reads `tunnel (seen just now)`, then `tunnel (seen Xm ago)` as time passes.
<Frame>
<img src="/images/tutorials/enroll-a-remote-node/nodes-table-online.png" alt="Nodes table listing Local with Online and docker.sock, media-server with Remote, Pilot Agent, tunnel (seen just now), and Online, and two other nodes that were already enrolled." />
</Frame>
</Step>
</Steps>
## Verify it worked
Check from two places, since either alone could be misleading (a stuck browser tab, or a table row that has not refreshed).
**The node switcher.** Click the switcher at the top of the sidebar. Your new node appears in the **Connected** list with an `AGENT` kicker and a `SEEN Xm AGO` timestamp.
<Frame>
<img src="/images/tutorials/enroll-a-remote-node/node-switcher-popover.png" alt="Node switcher popover headed Connected with the node count, listing Local with a star icon and LOCAL V0.98.0, media-server with AGENT and SEEN 1M AGO, and the other enrolled nodes, with a Manage nodes link at the bottom." />
</Frame>
**The Fleet view.** Switch back to Local if you navigated away, then open the **Fleet** tab. Every node shows as its own card with independent CPU, RAM, and disk stats pulled live from each host, and the new card carries a **Pilot** marker. The media-server card's running-container count reflects the containers already on that host, such as Jellyfin, plus the agent itself, not just an empty shell.
<Frame>
<img src="/images/tutorials/enroll-a-remote-node/fleet-view-two-nodes.png" alt="Fleet overview cards in a grid: Local, and media-server with a Pilot marker showing 3 running containers and 0 stopped, with independent CPU, RAM, and disk gauges on each card, alongside the other enrolled nodes." />
</Frame>
Click the node switcher and select your new node to confirm you can operate against it directly. Every top-level view scoped to that host, Home, Resources, Networking, Security, works the same way it does for Local. Hub-only views (Fleet itself, GitOps, Schedules, Audit, Logs, Update) stay hidden until you switch back to Local, because they only make sense from the hub.
## If something goes wrong
The most common first-time snag is the 15-minute enrollment token expiring before you run `docker compose up -d`, usually because of a copy-paste detour or a slow SSH session. The node row stays on `tunnel (waiting)` indefinitely; it does not time out or show an error on its own. If more than 15 minutes have passed, open **Edit node** on that row (the pencil in the row's actions), click **Regenerate enrollment token**, and repeat the save-and-run-compose steps with the fresh file. The previous tunnel is disconnected automatically, so there is nothing to clean up on the remote host first.
<Frame>
<img src="/images/tutorials/enroll-a-remote-node/edit-node-regenerate.png" alt="Edit node dialog for the media-server Pilot Agent node, with Name, a read-only Type, Mode, and Compose Directory, and a card reading 'Re-enroll the agent if the container was lost or the enrollment token expired. The previous tunnel is disconnected automatically.' with a Regenerate enrollment token button." />
</Frame>
## Related
<CardGroup cols={2}>
<Card title="Multi-node" icon="sitemap" href="/features/multi-node">
Distributed API Proxy mode, Sencho Mesh, license inheritance across nodes, and full troubleshooting.
</Card>
<Card title="Pilot Agent" icon="satellite-dish" href="/features/pilot-agent">
Credential lifecycle, resource limits, and pilot-specific troubleshooting for the tunnel itself.
</Card>
</CardGroup>