The tyranny of the keyboard

A server in the data center dying is one thing; a node in the field dying is a truck roll. Edge fleets deploy software to kiosks, gateways, and other nodes in the field that the data center rarely sees:

  • the node is in an inconvenient place: a yard, a roof, or a vehicle
  • the connection is intermittent, NATted, and/or firewallded
  • there is no monitor, no keyboard, and no one nearby
  • the hardware is heterogeneous: different controllers, different images, different peripherals
  • when it dies at 2am the answer is not to send someone with a laptop

Zero-touch is the discipline that ensures operators don't have to log into a node to change it: changes happen elsewhere, in a single source of truth, and nodes are forever converging on the right state.

This article describes patterns I look for before declaring an edge fleet production-grade. Some are related to provisioning, others to recovery, and others still are general practices that prevent a single bad deployment from requiring a site visit to kill and reimage:

  1. Provisioning,
  2. Remote recovery,
  3. Operations - visibility and networking.

Provisioning

Pattern 1: Derive identity from hardware, not a provisioning ceremony

The first question any new node asks itself is "what am I", and the wrong answers all begin with some sort of ceremony: QR codes, provisioning wizards, factory flashing - any process that touches every unit in the fleet and requires a human to do something special. Zero-touch provisioning begins with deriving the device's identity from something that is already on the device: most often, a network interface's factory-assigned, globally unique MAC address. A MAC address is a useful hint for inventorying devices, but should not be used as the cryptographic trust anchor if the platform supports something better.

Enrollment detail: On every boot the agent discovers its platform-specific device ID and uses it to discover the node's pending configuration, without any human touching an enrollment code. The device ID is only a lookup key; the fleet service maintains a strict inventory and requires proof of possession of the device's credential before giving the node production configuration, and new units can be enrolled in a restricted, pending state until that happens.

GET /nodes/{device-id}/config

This allows a spare unit to be swapped in for a failed one simply by plugging it in and waiting for it to self-enroll and discover its role. Note that a device ID is not a credential, so the question of how a device proves that it is what it claims to be is a separate topic (see Pattern 5).

Pattern 2: Pull-based reconciliation from a single source of truth

A push-based deployment that requires the orchestrator to open connections to every node in the fleet is fragile behind NATs, firewalls, intermittent connections, and reboots. The same goes for outbound control channels and queued commands, and a pull-based reconciliation loop gives every node a single, simple way to recover from any of these interruptions.

Node behavior: The agent pulls an authenticated, versioned deployment manifest on a jittered schedule and converges toward it. A pull failure preserves the last-known-good stack and uses exponential backoff and jitter before retrying. If node receives a push notification it will still use the same reconciliation logic to recover, but the shorter interval reduces the amount of time it must wait before it can check for new changes.

 +----------------------------------------------------------+
 | Wake on interval                                         |
 +-----------------------------+----------------------------+
                               |
                               v
 +----------------------------------------------------------+
 | GET target config from fleet API                        |
 +----------------------+-----------------------------------+
        request failed  | got config
               |        v
 +--------------------+  +----------------------------------+
 | Keep last good;    |  | Validate version and signature   |
 | retry with jitter  |  +----------------+-----------------+
 +---------+----------+                   |
           |                              |
           |                              v
           |             +----------------------------------+
           |             | Regenerate deployment manifest   |
           |             +----------------+-----------------+
           |                              |
           |                              v
           |             +----------------------------------+
           |             | Spec changed from on-disk state? |
           |             +------------+---------------------+
           |                     no    | yes
           |              +------------+------------+
           |              |                         |
           |              v                         v
           |     +----------------+      +--------------------+
           |     | Reconcile live |      | Write and redeploy |
           |     | runtime state  |      +----------+---------+
           |     +--------+-------+                 |
           |              |                         |
           |              |                         v
           |              |              +--------------------+
           |              |              | Sync assets        |
           |              |              +----------+---------+
           |              |                         |
           +--------------+-------------------------+
                          |
                          v
                     Next interval

The agent retrieves manifest changes periodically and on boot and uses them to update the node's configuration, which includes validating the manifest before accepting a new version. It always stores the last-known-good version to tolerate outages, and uses backoff and jitter to prevent the fleet from resynchronizing at the same time. Configuration changes and live runtime drift are reconciled separately so that an unchanged manifest doesn't hide a failed service.

Pattern 3: Make regeneration idempotent, and detect no-ops

If a reconciliation loop runs every few minutes it is likely to encounter situations where the target configuration hasn't changed since the last reconciliation. Regenerating configuration or restarting services unnecessarily is wasteful, harmful to availability, and dangerous in the face of partial failures.

Quiet-loop rule: Regenerate a canonical config manifest from the current configuration and compare its semantic contents or digest to the target configuration. Avoid writing files or restarting services when the target hasn't changed, but still detect and repair drift in the services' runtime state.

The agent uses a small embedded builder to regenerate the manifest from known parameters and compare the canonical representation to the target on-disk manifest. If they are equal, it avoids writing and restarting, but still uses an idempotent check to ensure that declared services, networks, mounts, and conditions are all present and correct. The separation between manifest versions and runtime parameters allow it to be quiet during steady-state reconciliation without ignoring runtime failures.

Pattern 4: Detect the environment at runtime; don't bake it into the config

A heterogeneous fleet is the norm, not the exception: different hardware, OS versions, accessories, and even accelerators with different drivers. Any configuration that hardcodes these differences and requires a human to manually refresh will incur ongoing operations overhead, especially if these differences are discovered at deployment time. Detecting environment variables at runtime and adapting the configuration to them remove this burden.

Hardware note: The agent detects its hardware and runtime environment (board revision, accelerator version) from the device tree and kernel, and uses that to find a compatible deployment manifest. This could mean selecting the right GPU stack for a compatible version of a TPU, or preferring a newer OS for a board that supports it. The manifest is versioned and tied to an immutable digest to tolerate upgrades.

When the OS is updated to a newer version of the accelerator runtime, the next reconciliation will select a compatible release from a policy-defined compatibility list and apply an immutable digest. Hardware, OS, driver, and runtime versions are all observed variables that affect policy, not trusted configuration. The observed facts can be cached to tolerate reboots, but the agent re-detects them and validates against the policy; the release policy still decides what combinations are allowed.

Pattern 5: Bootstrap trust without long-lived cloud access keys

The ability to say "I am what I claim to be" is identity (Pattern 1), and the ability to say "this node is allowed to do what it is doing" is trust. Trust is more involved: it requires secrets, and long-lived cloud access keys baked into binaries are the opposite of zero-touch. They rotate rarely, and when they are compromised the entire fleet is at risk. Short-lived, machine-authenticated credentials are much better.

Credential pattern: This means using service-to-service tokens where appropriate and limiting the scope of long-lived credentials. If the node authenticates to a cloud provider prefer an X.509 certificate to assume a scoped IAM role, instead of embedding long-lived credentials in the binary.

API requests carry a bearer token that is renewed before expiation, with allowance for clock skew. If the response indicates that the token is invalid the client will refresh and retry, but any other 4xx/5xx response should not cause an automatic refresh. The error handling for audience mismatch, enrollment failures, and authorization rejections is separate from token expiration and should be handled separately.

An equivalent pattern in the cloud is to use an X.509 client certificate to obtain temporary credentials for a scoped IAM role. This avoids long-lived credentials, but not device identity: certificates and private keys are credentials, and scoped to the instance. Treat them as such: hardware-backed where possible, protected at rest, rotated, expired, and revoked via the trust system. Token renewal follows the same pattern as reconciliation, and doesn't disrupt the node unless it fails. Long-lived credentials with insufficient rotation or revocation capabilities are best avoided.


Remote recovery

Provisioning patterns are about getting healthy nodes into the fleet; the zero-touch discipline is really about what happens when those nodes inevitably fail. The long tail of incidents consisting of a wedged process or failed deployment are the ones where "call someone to come log in" is not an option. Defense in depth is the guiding principle for remote recovery: building several independent mechanisms that keep the node healthy, each of which can recover from a subset of possible failures, including the failure of other mechanisms.

Pattern 6: Decouple what to run from when to run it

The most important recovery decision is architectural: the agent that decides what to run cannot be the only thing that knows how to run it. Otherwise, a crash of the agent process makes the node unrecoverable by automation.

Recovery detail: The agent stages a signed, versioned, declarative manifest in a non-executable location before applying it, and a fixed supervisor-owned reconcile command validates the manifest's signature, schema, and image digests, and allowed operations. A service manager and timer can invoke this command independently of the agent. Do not allow the agent to replace the executor or inject shell commands into a privileged chain.

# Fixed, root-owned reconcile command; the staged manifest is data, not code.

acquire a single-instance lock

verify manifest signature, version, schema, and allowed operations

pull approved immutable digests with bounded retries and backoff

converge the declared stack and remove only explicitly managed orphaned services

The privileged executor has a deliberately narrow contract: it can validate target state, reconcile declared resources, and ask the service manager to restart the agent. Strict ownership, atomic file replacement, replay protection, and a single-instance lock protects the handoff. If the agent crashes, the service manager can restart it, while the independent timer can continue to invoke the fixed reconcile command. The scheduling path survives an agent failure without executing agent-injected code with elevated privileges.

Pattern 7: Stack recovery in layers

Below the heartbeat, cheaper and faster mechanisms handle the common causes of failure before they escalate.

Recovery layer What it covers How it recovers from it
Individual services crashing A service exits prematurely Run it again, ideally under a restart policy
Agent watchdog / dead man switch The agent process is wedged Kill and restart it, after detecting it is not healthy
Independent reconcile timer (Pattern 6) The agent process is dead Invoke the fixed, validated reconcile command

Failure mode: Services can use a restart policy, but match it to the expected behavior: include health checks, backoff, bounded retries or escalation, and an operator-visible reason for repeated failures. An unbounded restart loop can waste resources and hide other problems. The agent watchdog can kill the agent process if it detects that it is not healthy, and the supervisor applies the recovery policy.

The self-restart is an acceptable, if last resort, way of recovering from a generic failure. This pattern complements Pattern 6 by giving the service manager an explicit, rate-limited tool when the agent is not healthy. The independent reconcile timer manages the desired state and does not have to handle generic failure modes.

Pattern 8: Keep one big red button

Automation handles the common cases, but sometimes a running node needs to be placed in a known-good state. The "big red button" pattern provides an out-of-band, authenticated way of doing this, without exposing dangerous low-level operations to the wider world.

Reset path: This can be implemented as an authenticated, audited request to cause the next deploy to recreate a defined subset of the stack. Destructive operations on data volumes require explicit authorization, backup, or recovery path, and should never be triggered from a writable file.

if an authenticated reset operation is pending:

load its operation ID, scope, and last completed stage

verify authorization, approval, and backup state

run safely repeatable stages and checkpoint each completion

bring the declared stack up and mark the operation complete

A destructive reset should not rely on clearing a flag after completing, because a crash between an external side effect and clearing that flag could cause the same operation to run twice. Give each request an operation ID and a durable scope, such as resources, cached data, or enrollment state. Complete each stage safely and repeatably, and retain a terminal, auditable state. This allows recovery from crashes while preserving authorization and avoiding duplicates without claiming to provide exactly-once execution that a file flag cannot.

Pattern 9: Anchor to a known-good image

Both first boot and factory reset are bootstrapping procedures that get the node to a known healthy state where it can run any user applications. These processes all benefit from a tested bootstrap release, but the production recovery process should use an immutable image digest or versioned artifact, rather than an unvalidated mutable tag.

Bootstrap detail: CI/CD can provide a human-readable channel like 'starter' for bootstrap discovery, but the release service resolves that channel to a signed, immutable digest, and records the previous known-good version. Enrollment and reset processes install the known-good digest, verify its integrity, and retain a rollback path, rather than using an arbitrary latest image.


Operations

Pattern 10: Push observability outward

If a monitoring system has to reach into nodes to pull information from them, it has the same firewall and NAT traversal challenges as any other client traffic. The observability pushes information out over the same path nodes use to reach the fleet.

Fleet signal: The receiver stores structured event data with stable node and event identifiers. The agent persists a bounded local spool during outages and retains timestamps and sequence information for replay, and the receiver deduplicates events with the same identifiers. Inventory, housekeeping, and credential rotation events let a dashboard answer questions such as "what version is this node running", without reaching into nodes. Local health endpoints can support node diagnostics, if needed.

Pattern 11: Provision the network the same way you provision software

Apply the zero-touch discipline to the network. A distributed fleet is best served by a self-installing overlay, which handles heterogeneity and minimizes the privileges of individual nodes.

Network detail: The agent provisions the overlay on first boot by installing a supported WireGuard implementation or userspace fallback, limiting the permissions it uses to establish tunnels, and registering through a minimal bootstrap channel. WireGuard identifies peers by their public key, but does not provide fleet enrollment, key distribution, rotation, or revocation; those responsibilities fall to an external coordination service, which authorizes devices, distributes public keys, rotates or revokes identities, and reports health. Network changes are versioned, staged, rollback-capable, and protected by a connectivity-preserving failsafe.


A note on control plane design

One implementation choice that appeared in several patterns was the control plane: a separate process that communicated with the agent over some medium. Does the plane run in a sandbox such as a container, and if so, which capabilities does it need to bind mounts or IPC?

Approach Description Security considerations
Mount the runtime socket Bind the host's runtime socket into the sandbox The socket may grant control equivalent to root
Remote-shell into the host Establish a connection to the host with known credentials Requires credential storage, rotation, and fine-grained command authorization
Run a host service Run the agent with only the host privileges it requires Self-contained local control, but the service itself is a privileged trust boundary

The failure mode often appears during recovery, not during the happy-path deployment. An initial remote-shell setup may work well while credentials are fresh and every node has the same layout, but months later a password rotation misses one site, an SSH command fails to find the right directory, or a quoting bug turns a repair into a privilege escalation. The node remains healthy, but the recovery tools now require their own recovery.

Once the management component can perform arbitrary operations on the host, it is part of the host's trusted computing base, regardless of whether it runs in a container. For most fleets the clean design is a narrowly-scoped host service when host privileges are required, without exposing the runtime socket to general-purpose application code, and with limited command auditing and update capabilities.

The question is not "container or host?" in isolation, but "what can this component do to the machine when it is wrong, compromised, or half-updated?"


Checklist

This is the list of questions to ask to determine if a system is truly zero-touch.

Provisioning

  • Can a fresh node be enrolled by simply plugging it in? (Pattern 1)
  • Does the node reconcile its state by fetching target configuration from the fleet API? (Pattern 2)
  • Does reconciliation avoid useless changes while still repairing live-runtime drift? (Pattern 3)
  • Does the node detect its environment at runtime, rather than have it hardcoded? (Pattern 4)
  • Are secrets on the node either short-lived or centrally revocable? (Pattern 5)

Recovery

  • Can the node's stack recover even if the agent process is dead? (Patterns 6, 7)
  • Is there a watchdog that recovers the agent by restarting it into a clean state? (Pattern 7)
  • Are reset operations authenticated, scoped, durable, auditable, and safely repeatable? (Pattern 8)
  • Is there a stable, immutable version reference for first boot and reset? (Pattern 9)

Operations

  • Does monitoring information come from nodes, rather than the other way around? (Pattern 10)
  • Is the overlay network self-installing? (Pattern 11)
  • Is the control plane process privilege model matched to the risk surface?

Conclusion

All these patterns have one thing in common: give the node enough rope to tie itself up, but not enough to hang itself. The control plane maintains identity, policy, release authorization, and target state; narrow node-side reconcilers observe the environment, apply only their declared scope, retain known-good state, and report outward. This is the essence of zero-touch: making routine operations a recoverable property of the system while keeping privilege boundaries and exceptional operations explicit.