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/arm64devices. - 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 discoverCheck the target operating systems and architectures:
wendy device info --device device-a
wendy device info --device device-bBoth 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-hostThe 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-demoSave 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 --detachBoth --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 deliveredRead 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-bFind 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-aOpen a second terminal for the other device's tunnel:
wendy cloud tunnel 18082:8080 --device device-bBack 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/healthEach 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 --detachRepeat 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-bPress Ctrl+C to stop viewing logs. To retry only device-b, use:
wendy run --build-host build-host --device device-b --detachThis submits another build, which can reuse the build host's cache. It does not resume the previous delivery.
Troubleshooting
| Symptom | What to check |
|---|---|
| The build host cannot deliver to several devices | Update its agent to a version with multi-target delivery support. |
| A target has a different platform | Split the targets into groups with matching operating systems and CPU architectures. |
| The image built but delivery failed | Check target enrollment, mesh connectivity, and the registry error in the output. |
| The app starts but a health request fails | Check that port 8080 is free, both tunnel processes are running, and the app logs show that the HTTP server started. |
The CLI rejects --builder | Remove 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-bIf you enabled the builder role only for this exercise, disable it:
wendy device build-host disable --device build-hostDisabling the role stops future remote builds. It does not delete the build host's cached layers or source context.