WendyOS Docs

Build Once, Deploy to Several Devices

Use a Wendy build host to build one container image, deploy it to two devices, and check the app on each device.

This tutorial deploys the same small HTTP service to two WendyOS devices. A third device builds the image and delivers it to both targets. You then request the service from each target to confirm that both are running the expected revision.

For several cooperating services on one device, follow Multi-App Deployments.

Prerequisites

  • The Wendy CLI on your development machine.
  • Two Linux devices with the same CPU architecture, such as two linux/arm64 devices.
  • A Linux build host running the Wendy agent and BuildKit, with support for the target platform and at least 8 GiB free on its BuildKit cache filesystem.
  • All three devices enrolled in the same Wendy Cloud organization, with working connections between the build host and the targets.
  • A signed-in user account in that organization. Enabling the builder role and submitting builds require a user certificate.
  • Current CLI and agent versions that support delivery to multiple targets.

Your development machine does not need Docker for this workflow. The build host needs internet access to download the Python base image. This example uses a single-service Dockerfile project; remote builds do not support Compose or a services map in wendy.json.

The commands use build-host, device-a, and device-b as device names. Replace them with the enrolled names of your devices.

Check the devices and build host

Sign in and list your devices:

wendy auth login
wendy --json cloud discover

Check the target operating systems and architectures:

wendy device info --device device-a
wendy device info --device device-b

Both targets must have the same platform. One image cannot serve a mix of linux/arm64 and linux/amd64 targets. For GPU applications, also check that the image and GPU runtime are compatible with every target. The first target supplies the build's device and GPU hints.

Enable the builder role on the machine you intend to use for builds:

wendy device build-host enable --device build-host
wendy device build-host status --device build-host

The status should report Builder role: enabled, BuildKit: available, and support for your targets' platform under Builds or Emulates. Check that Cache reports enough free space. Enabling the role does not install BuildKit. See the build host reference if BuildKit is unavailable.

The builder role permits users in your organization to submit builds to this machine. Use a host intended for shared builds. A Mac can run the CLI, but cannot serve as the remote build host.

Create the service

Create an empty project directory:

mkdir fleet-demo
cd fleet-demo

Save this as app.py:

import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

REVISION = "fleet-demo-v1"


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path != "/health":
            self.send_error(404)
            return

        body = json.dumps({"status": "ok", "revision": REVISION}).encode()
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)


if __name__ == "__main__":
    print(f"Serving {REVISION} on port 8080", flush=True)
    ThreadingHTTPServer(("0.0.0.0", 8080), Handler).serve_forever()

Save this as Dockerfile:

FROM python:3.12-slim
WORKDIR /app
ENV PYTHONUNBUFFERED=1
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]

Save this as wendy.json:

{
  "$schema": "https://wendy.dev/schemas/wendy.json",
  "appId": "fleet-demo",
  "version": "0.1.0",
  "platform": "linux",
  "entitlements": [
    { "type": "network", "mode": "host" }
  ]
}

Host networking makes port 8080 available on each device. The app uses that device's network stack, so another app must not already be listening on the same port.

Build and deploy

Run this from the project directory:

wendy run --build-host build-host --device device-a,device-b --detach

Both --build-host and --detach are required when --device names several targets. The CLI checks target compatibility, sends the source to the build host, and requests one image build. The build host pushes that image to each target's registry. Wendy then creates and starts the app on each target.

A successful run ends with a report like this:

Deployed from one build on build-host:
  device-a                 delivered
  device-b                 delivered

Read every row and check the command's exit status. A container start failure appears in the per-device report and makes the command fail. Earlier build or image-delivery errors can stop the command before this final report.

Deployment is not atomic: an error can leave some devices updated and others unchanged. Wendy does not roll successful devices back automatically. Detached deployment also skips readiness probes, so check the service on both devices next.

Verify both apps

List the apps on each target:

wendy --json device apps list --device device-a
wendy --json device apps list --device device-b

Find fleet-demo in each result and check that its runningState is RUNNING and its version is 0.1.0.

Open a cloud tunnel to the first device in a separate terminal and leave it running:

wendy cloud tunnel 18081:8080 --device device-a

Open a second terminal for the other device's tunnel:

wendy cloud tunnel 18082:8080 --device device-b

Back in your project terminal, request both services:

curl --fail --max-time 10 http://127.0.0.1:18081/health
curl --fail --max-time 10 http://127.0.0.1:18082/health

Each request should return:

{"status": "ok", "revision": "fleet-demo-v1"}

These responses confirm that the expected app revision answers requests on both devices. A delivered row or a running container alone does not establish that.

Deploy a change

Change REVISION in app.py to fleet-demo-v2 and set the version in wendy.json to 0.2.0. Run the same deployment command again:

wendy run --build-host build-host --device device-a,device-b --detach

Repeat both health requests. Each should now return fleet-demo-v2. If one device failed, inspect its logs and fix the reported problem before retrying that target:

wendy device logs --app fleet-demo --device device-b

Press Ctrl+C to stop viewing logs. To retry only device-b, use:

wendy run --build-host build-host --device device-b --detach

This submits another build, which can reuse the build host's cache. It does not resume the previous delivery.

Troubleshooting

SymptomWhat to check
The build host cannot deliver to several devicesUpdate its agent to a version with multi-target delivery support.
A target has a different platformSplit the targets into groups with matching operating systems and CPU architectures.
The image built but delivery failedCheck target enrollment, mesh connectivity, and the registry error in the output.
The app starts but a health request failsCheck that port 8080 is free, both tunnel processes are running, and the app logs show that the HTTP server started.
The CLI rejects --builderRemove it when using --build-host. It selects a local builder and cannot be combined with a remote build host.

Clean up

Press Ctrl+C in both tunnel terminals. Remove the tutorial app and its image from each target:

wendy device apps remove fleet-demo --force --cleanup --device device-a
wendy device apps remove fleet-demo --force --cleanup --device device-b

If you enabled the builder role only for this exercise, disable it:

wendy device build-host disable --device build-host

Disabling the role stops future remote builds. It does not delete the build host's cached layers or source context.

On this page