HELP

nodex CLI

Every command, flag and file the nodex CLI reads when it pushes a release.

The nodex CLI

nodex runs on your own computer or in CI. It turns your code into a release for a fleet: it builds or pulls every service's image, uploads it to the Nodexa registry and marks the release complete. Every device in the fleet then updates to it, unless the device or fleet is pinned to another release.

This is not the nodex command on a device. That one only talks to the device's local supervisor; see Debugging.

Commands

nodex push [flags]     # build, push and release
nodex --version        # installed version
nodex push --help      # every flag, with examples

push is the only command. Everything else is set with the flags below, a manifest file, or environment variables.

Install

SystemHow
macOS, Linux (x86_64 or ARM64)The install script. It downloads the latest release to /usr/local/bin, asking for sudo if needed
Windows (x86_64)Download nodex_windows_amd64.tar.gz from the latest GitHub release and unpack it into a folder on your PATH
Anything with Gogo install, or build from source

macOS and Linux

curl -fsSL https://raw.githubusercontent.com/adhuldas/nodexa-cli/main/install.sh | sh
nodex --version

Windows (PowerShell)

curl.exe -fsSL -o nodex.tar.gz https://github.com/adhuldas/nodexa-cli/releases/latest/download/nodex_windows_amd64.tar.gz
$null = mkdir -Force $HOME\bin
tar -xzf nodex.tar.gz -C $HOME\bin
& $HOME\bin\nodex.exe --version

Add $HOME\bin to your PATH to run it as plain nodex.

With Go

go install github.com/adhuldas/nodexa-cli/cmd/nodex@latest

From source

git clone https://github.com/adhuldas/nodexa-cli.git
cd nodexa-cli
go build -o /usr/local/bin/nodex ./cmd/nodex

Pushing containers needs Docker on the same machine. Multi-platform builds also need docker buildx. Pushing ESP32 WebAssembly modules doesn't need Docker.

Authentication

Every push needs a Fleet ID (copy it from the fleet's summary card) and a deployment token (the fleet's Deployment Token button). Pass them as flags or environment variables; flags win when both are set.

Environment variableSame asNotes
NODEXA_FLEET_ID--fleet-idRequired
NODEXA_API_TOKEN--tokenRequired. Keep it in a secret store, never in the repository
NODEXA_PLATFORM--platformOptional target CPU for every service

In CI

# Any CI system: store both values as secrets, then
export NODEXA_FLEET_ID="$FLEET_ID"
export NODEXA_API_TOKEN="$DEPLOY_TOKEN"
export NODEXA_PLATFORM="linux/arm64"     # optional
nodex push

nodex push flags

FlagDefaultWhat it does
--fleet-id <id>$NODEXA_FLEET_IDFleet that gets the release. Required
--token <token>$NODEXA_API_TOKENFleet deployment token. Required
-f, --file <path>nodexa.ymlManifest to read. Also accepts a compose file
-c, --compose-file <path>noneCompose file to read. Takes priority over -f
-s, --service <name>folder nameService name for a lone Dockerfile; with a compose file, pushes only that service
--dockerfile <path>DockerfileDockerfile to build when there is no manifest or compose file
--context <dir>.Build context for that Dockerfile
-p, --platform <list>$NODEXA_PLATFORMTarget CPU, e.g. linux/arm64. Comma-separate several to build one multi-platform image
-r, --registry-file <path>auto-detectedCredentials for private registries you pull from
-h, --helpShow help and examples

How nodex finds your services

It uses the first of these that applies, and prints which one it picked:

  1. A compose file given with -c
  2. A file given with -f
  3. nodexa.yml or nodexa.yaml in the current folder
  4. docker-compose.yml, docker-compose.yaml, compose.yml or compose.yaml

    Skipped if it has no services nodex can build or pull.

  5. A Dockerfile in the current folder (or the --dockerfile path)

    One service, named with --service or else after the folder: lowercased, anything other than letters, digits, - and _ turned into -, up to 63 characters.

  6. --service and --dockerfile together, even from another folder

If none apply, the push stops with a message listing these options.

nodexa.yml reference

Use a manifest for several services, pre-built images, or ESP32 modules.

platform: linux/arm64            # optional default for every service

services:
  - name: web                    # required; lowercase, used as the service name on devices
    dockerfile: Dockerfile       # default: Dockerfile
    context: .                   # default: .
  - name: worker
    dockerfile: worker/Dockerfile
    context: worker/
    platform: linux/arm/v7       # overrides the top-level platform
  - name: firmware
    image: ghcr.io/my-org/firmware:v1.2.0   # pre-built: pulled, re-tagged, pushed
  - name: sensor
    wasm: build/sensor.wasm      # ESP32 only; can't be combined with image or dockerfile
FieldRequiredMeaning
platformNoDefault target CPU for every service
services[].nameYesService name. Must be unique in the file
services[].dockerfileNoDockerfile to build. Default Dockerfile
services[].contextNoBuild context. Default .
services[].imageNoExisting image to pull, re-tag and push instead of building
services[].platformNoTarget CPU for this service only
services[].wasmNoPath to a built .wasm module for ESP32 fleets. Can't be combined with image or dockerfile

A file whose services: is a map instead of a list is read as a compose file, even when passed with -f.

docker-compose.yml support

Only the parts that decide what to push are read: build (a path, or context, dockerfile and platform), image and platform. Ports, volumes, environment and the rest are ignored.

services:
  api:
    build:                       # built from source
      context: ./api
      dockerfile: Dockerfile.prod
      platform: linux/arm64
  web:
    build: ./web                 # short form: just the context
  cache:
    image: redis:7-alpine        # pre-built: pulled, re-tagged, pushed
  worker: {}                     # no build or image: uses ./worker/Dockerfile if present
  • A service with build is built from source.
  • A service with only image is pulled and pushed as-is.
  • A service with neither uses <service-name>/Dockerfile if it exists.
  • If a service is still left without a Dockerfile and a Dockerfile sits next to the compose file, that service is built from it. nodex prefers the service named with -s, then the one named after the folder.

Target platform

Build for the CPU your devices have. From highest priority:

  1. --platform or NODEXA_PLATFORM: replaces the platform of every service
  2. The service's own platform (in nodexa.yml, or in compose build or the service)
  3. The top-level platform in nodexa.yml
  4. Otherwise, your computer's own CPU
DevicesPlatform
Raspberry Pi 4, Variscite DART, 64-bit ARM boardslinux/arm64
BeagleBone Black and other 32-bit ARM boardslinux/arm/v7
Nodex Nomad and other x86_64 PCslinux/amd64

A comma-separated list (for example linux/arm64,linux/arm/v7) builds one multi-platform image with docker buildx, so one release serves mixed fleets.

Private registries

If an image or base image lives in a private registry (GitHub, Docker Hub, AWS ECR, GitLab), nodex logs in first using a credentials file. It looks in the current folder for registry.yml, registries.yml, .registry.yml or .registries.yml (or .yaml), or takes a path from -r.

# Format 1: keyed by registry host (recommended)
ghcr.io:
  username: my-github-username
  password: ghp_personalAccessTokenWithReadPackages
docker.io:
  username: my-docker-username
  password: dckr_pat_secretToken

# Format 2: the same map under "registries:" or "auths:"
registries:
  ghcr.io:
    username: my-github-username
    password: ghp_personalAccessTokenWithReadPackages

# Format 3: a list
- registry: ghcr.io
  username: my-github-username
  password: ghp_personalAccessTokenWithReadPackages

Never commit this file. Add it to .gitignore:

echo "registry*.yml" >> .gitignore

What a push does

  1. Logs into any private registries from the credentials file
  2. Finds the services, as described above
  3. Reserves the next revision number for the fleet
  4. Logs into the Nodexa registry with your token (skipped when only WebAssembly modules are pushed)
  5. For each service, builds or pulls the image, pushes it and records its digest

    WebAssembly modules are checked and uploaded directly, without Docker.

  6. Marks the release complete; devices start updating on their next check-in

If any step fails after the revision is reserved, the release is marked failed and devices keep running what they had. Fix the problem and push again; the next push gets a new revision.

Example output

🔍 compose file: docker-compose.yml
   Found 2 service(s): [api web]

📝 Reserving release for fleet <fleet-id>...
   Reserved revision 17

🔑 Logging into registry nodexa.elzora.tech...

🔨 Building and pushing 2 service(s)...
   ✅ api → sha256:…
   ✅ web → sha256:…

✅ Completing release revision 17...
   Release 17 is now complete

🎉 Push complete!

Examples

# Folder with a Dockerfile; service name taken from the folder
nodex push --fleet-id <fleet-id> --token <api-token>

# Same, but name the service
nodex push --fleet-id <fleet-id> --token <api-token> --service backend

# Dockerfile somewhere else, with its own build context
nodex push --fleet-id <fleet-id> --token <api-token> -s api --dockerfile docker/api.Dockerfile --context .

# A compose file, or just one service from it
nodex push --fleet-id <fleet-id> --token <api-token> -c docker-compose.yml
nodex push --fleet-id <fleet-id> --token <api-token> -c docker-compose.yml -s api

# A manifest at a custom path
nodex push --fleet-id <fleet-id> --token <api-token> -f deploy/staging.yml

# 32-bit ARM boards (BeagleBone), or one image for several CPUs
nodex push --fleet-id <fleet-id> --token <api-token> --platform linux/arm/v7
nodex push --fleet-id <fleet-id> --token <api-token> --platform linux/arm64,linux/arm/v7

# Private base images
nodex push --fleet-id <fleet-id> --token <api-token> -r ~/.config/nodexa/registry.yml

Error messages

no nodexa.yml manifest, docker-compose.yml, or Dockerfile found

Run from your project folder, or point nodex at the files with -c, -f, or --service with --dockerfile.

required flag(s) "fleet-id", "token" not set

Pass --fleet-id and --token, or set NODEXA_FLEET_ID and NODEXA_API_TOKEN.

reserving release: HTTP 401 or HTTP 403

The token is wrong, revoked, or for another fleet. Generate a new one from the fleet's Deployment Token dialog and check the Fleet ID.

service[0].name is required or at least one service is required

Every entry under services: in nodexa.yml needs a name, and the list can't be empty.

wasm can't be combined with image or dockerfile

A WebAssembly service only takes name and wasm.

… is not a WebAssembly module

The wasm path points at something else, such as a source file or a native binary. Build with the command under Deploy apps → ESP32 and point at the .wasm output.

login to external registry … failed

Check the username and password in the credentials file. GitHub needs a token with read:packages.

exec format error on the device

The image was built for the wrong CPU. Push again with the right --platform from the table above.