Skip to content
OpenRelay is in early access, and the /v1 API is stable. New capabilities ship in the changelog.
GuidesProviders

Interruptible nodes

Onboard a GPU container you rent from another market or cloud as Interruptible capacity with one command, and tell OpenRelay when the market takes it back.

Interruptible capacity is a GPU container you rent somewhere else: a spot instance, a GPU marketplace, a cloud container service. The market can take it back at short notice, and it often comes without root, without systemd, and with an image you did not build. You onboard it on your own provider account with one command, run inside the container. Nothing is installed outside one directory, and the command needs no sudo.

Registered and monitored, not scheduled or billed

This first release registers an Interruptible node, tracks whether it is alive, and takes its termination warnings. Nothing is scheduled on it and nothing is billed from it, so it earns nothing yet. Scheduling and earnings come in a later release. Interruptible nodes never appear in customer capacity listings.

Interruptible capacity is NVIDIA-only for now: the agent reads the GPUs through nvidia-smi. It is a separate path from VM and pod nodes: it has its own token class, its own installer, and a different lifecycle (a restarted container is a new node).

Requirements

You need an org that is an approved provider (see Providers). The container needs:

NeedDetail
RuntimeLinux on amd64 or arm64, under gVisor (runsc) or runc.
GPUAt least one NVIDIA GPU, and an nvidia-smi on PATH that answers nvidia-smi -q -x. nvproxy (gVisor) and the NVIDIA container toolkit (runc) both provide it.
ShellA POSIX sh with uname, mkdir, chmod and sha256sum (coreutils or busybox). Distroless and scratch images do not have them.
Downloadercurl or wget, with a CA bundle. With neither, see No curl or wget.
Outbound HTTPSTCP 443 to api.openrelay.inc and to the artifact download URLs, with working DNS.
Outbound UDPUDP 443 to the OpenRelay gateway (the QUIC data tunnel) and UDP 4242 to the overlay lighthouses. The agent also asks public STUN servers for its NAT mapping at start-up; that answer is informational, and a blocked STUN server only prints a warning.
One writable directoryFor keys, a lock file and the log. The agent takes the first that works: $OPENRELAY_INTERRUPTIBLE_DIR, /var/lib/openrelay-interruptible, $HOME/.openrelay-interruptible, /tmp/openrelay-interruptible.
ClockWithin 300 seconds of real time. Enrollment is signed, and the signature carries a timestamp.

Not required: root, systemd or any init system, being PID 1, NET_ADMIN or NET_RAW, /dev/net/tun, PCI sysfs or lspci, ip or iptables, a package manager, inbound ports, IPv6, a filesystem that survives a restart, or a cloud metadata server. The node reaches OpenRelay only through outbound connections: it punches through NAT where it can and relays through the lighthouses where it cannot.

Onboard a container

Mint an Interruptible provisioning token

orl provider provisioning-token create \
  --class interruptible --notice-seconds 30 --max-enrollments 64
FlagRangeDefaultMeaning
--classpersistent or interruptiblepersistentSet interruptible for this path. A persistent token is a regular VM or pod token.
--notice-seconds0 to 360030How much warning you can usually give before the market reclaims a container. Set 0 if you can give none. See Termination warnings.
--max-enrollments1 to 100064How many nodes this token may ever enroll.

The token (otk_…) lives 30 days and is shown once. Anyone holding it can enroll nodes into your account up to its cap, so keep it where you keep other secrets. Revoke it early with orl provider provisioning-token delete <id>. Nodes it already enrolled keep running. orl provider provisioning-token list shows it exhausted once it has enrolled --max-enrollments nodes.

Run the one command in the container

Use whichever downloader the image has. No sudo:

curl -fsSL "https://api.openrelay.inc/v1/provider/interruptible/bootstrap?token=otk_..." | sh

The script picks the agent build for uname -m, picks the state directory, downloads interruptible-agent, checks its sha256 and starts it. The agent prints each stage as it passes: probe (GPUs, the NAT mapping, the state directory), enroll, overlay, ops ssh, tunnel. Once the node is registered it moves itself to the background and the command exits 0, so a shell or a start hook that runs it does not block. If a stage fails, the agent stops and names the cause (see Troubleshooting).

Running the command again while the agent is still running reports the running node instead of starting a second one. To onboard every new container automatically, run the command from the container's start hook: that is what the 30-day token lifetime is for.

In a start hook, or anything that checks the exit status, fetch the script into a variable first. Piped straight into sh, a failed download hands it an empty script, which exits 0 without enrolling anything. This form fails when the download fails, returns the agent's status otherwise, and writes nothing to disk (the script embeds your token):

script=$(curl -fsSL "https://api.openrelay.inc/v1/provider/interruptible/bootstrap?token=otk_...") \
  && printf '%s\n' "$script" | sh

With wget, script=$(wget -qO- "...") && printf '%s\n' "$script" | sh.

Verify the node

orl provider node list --class interruptible

A new node shows pending until its tunnel is up, then online. The statuses:

StatusMeaningWhat moves it
pendingEnrolled; no heartbeat seen yetTo online on the first heartbeat. To removed if none arrives within 10 minutes of enrolling.
onlineTunnel up, heartbeats every 30 secondsTo offline after 60 seconds with no heartbeat: about 3 minutes after the container dies, at worst.
offlineHeartbeats stoppedBack to online if they resume (a network blip, an OpenRelay gateway deploy). To removed after 10 minutes offline in a row: about 13 minutes after the container dies.
drainingA termination warning arrivedTo removed at the deadline (terminateAt), even if the node is still heartbeating, or about 13 minutes after it goes quiet if that comes first.
removedGone for goodFinal. The node stays visible for about a day (25 hours) with orl provider node list --include-removed, then is deleted.

When a node is removed, its agent learns within 30 seconds, stops, deletes its keys and exits.

No curl or wget

Fetch the bootstrap URL on any machine that has a downloader and read the script: it names a download URL and a sha256 for each architecture (amd64 and arm64). The download URLs expire an hour after you fetch the script. Download the agent for the container's architecture, check its sha256, copy the binary into the container, and run it there:

chmod +x ./interruptible-agent
./interruptible-agent run --api https://api.openrelay.inc --token otk_...

The agent carries its own CA roots, so this path needs no CA bundle in the image. Add --state-dir <dir> to choose the state directory; without it the agent uses the order in Requirements.

Termination warnings

A market reclaims a container on its own schedule. How it warns you is between you and the market: a metadata endpoint, a signal, a webhook, an email, or nothing. OpenRelay does not watch any market or cloud API. If you can pass a warning on, use one of two doors:

With your org credentials, from wherever the market's warning lands (a webhook handler, a scheduler):

orl provider node offboard <nodeId> --notice-seconds 90
orl provider node offboard <nodeId> --terminate-at 2026-10-01T12:00:00Z

With neither flag the deadline is now.

A warning moves the node to draining at once and removes it at the deadline, or earlier if its heartbeats stop first. A deadline can be at most 24 hours ahead (--terminate-at, --at, --notice-seconds and --in alike); a time in the past means now. Sent with no time at all, a warning uses the token's --notice-seconds (30 by default). A deadline only ever moves earlier, so a late or repeated warning cannot extend a node's life.

Sending nothing is supported: the node goes offline when its heartbeats stop and is removed about 13 minutes after it goes quiet.

Stopping the agent itself with SIGTERM or SIGINT counts as a warning with a deadline of now (best effort). In most containers the agent is not PID 1, and a container stop sends SIGTERM only to PID 1, so the agent is killed outright and liveness removes the node instead.

Set --notice-seconds to what your market really gives

In this release the window only sets the deadline. Once scheduling ships, OpenRelay drains work inside that window, so a number longer than the market's real warning promises drain time that does not exist.

Offboard a node

To retire a healthy node now:

orl provider node offboard <nodeId>

With no deadline the node is removed in the same call. The agent sees the removal on its next 30-second poll and exits. orl provider node update <nodeId> --status removed also removes a node at once. Offboard before you stop a container if you can: otherwise the node lingers as offline, then is removed up to 13 minutes later.

Restarts: every run is a new node

An Interruptible node has no hardware identity, so every run of the command enrolls a new node with a new ID. Nothing restarts the agent when the container restarts (there is no systemd, and the agent is not PID 1):

  • Container restarted: the old node goes offline, then removed about 13 minutes later. Run the command again, by hand or from the start hook, to enroll a new node.
  • Command re-run after the node was removed: enrolls a new node if the token is still valid.
  • Two containers from the same image: two nodes.

Do not key anything on a node ID across restarts. Every enrollment counts against the token's --max-enrollments, restarts included.

What OpenRelay can access

  • A shell in your container. The agent runs an SSH server inside its own process, reachable only over OpenRelay's private overlay network (port 22022 on the node's overlay address). OpenRelay operators can open a session into the container, as the user the agent runs as, for support and to place workloads on the node. Sessions need a short-lived certificate (24 hours) from OpenRelay's ops SSH authority, issued for that node's ID. There is no password login and no inbound port on your side.
  • What the agent reports at enrollment: the driver version and each GPU's model, UUID, PCI address, memory and fabric state (from nvidia-smi -q -x); the container's hostname, CPU model, core count and RAM; and whether it runs under gVisor. Once running, it also reports its SSH host key. The rest of the probe (the NAT mapping, user ID, capabilities, tools) is printed in the container and never sent.
  • What it leaves alone: it does not use /etc/machine-id, DMI data or a cloud metadata server, and it watches no market API.

Limits

LimitValue
Nodes per token64 by default, up to 1000 (--max-enrollments). Re-runs after a restart count.
Token lifetime30 days, revocable early.
Notice window0 to 3600 seconds, default 30.
Removed-node historyAbout a day (25 hours, --include-removed).
GPU vendorNVIDIA, read through nvidia-smi.
NetworkOutbound UDP is required. A market that blocks all outbound UDP cannot run Interruptible nodes.

Troubleshooting

interruptible-agent probe prints what the agent sees in the container and changes nothing. interruptible-agent status prints the running agent's last state. The log is agent.log in the state directory.

Error or symptomCause / fix
no GPU visible to nvidia-smiThe container has no NVIDIA GPU attached, or no nvidia-smi on PATH. Start it with GPUs (nvproxy under gVisor, the NVIDIA container toolkit under runc) and check that nvidia-smi lists them.
nvidia-smi -q -x unparseablenvidia-smi answered but its XML report could not be read. Run nvidia-smi -q -x yourself: it must print a complete XML document. If it does, send the interruptible-agent probe output to support@openrelay.inc.
warning: NAT mapping unknownNo public STUN server answered. This is informational and the node still enrolls. If the node then stays pending, outbound UDP is blocked: see the next rows.
api unreachableNo HTTPS connection to api.openrelay.inc. Check DNS and outbound TCP 443.
no writable state directoryNone of the four directories in Requirements is writable. Set OPENRELAY_INTERRUPTIBLE_DIR to a writable path and run the command again.
WRONG_BOOTSTRAPThe token was minted without --class interruptible. Mint one with it. A regular token only works with the standard installer on a VM or pod host.
INTERRUPTIBLE_NO_REENROLLThe enrollment reused an identity key that already enrolled a node, live or removed. Interruptible nodes never re-enroll. interruptible-agent makes a fresh key on every run, so this means a different client sent the enrollment. Run the one command again.
INVALID_TOKEN or TOKEN_REVOKED (bootstrap URL), token expired or token revoked (agent)The token is unknown, past its 30 days, or revoked. Mint a new token. Running nodes are not affected.
TOKEN_ENROLLMENT_CAPThe token has enrolled --max-enrollments nodes, restarts included. Mint a new token.
enroll signature timestamp outside ±300s windowThe container's clock is more than 300 seconds off. Fix the clock and run the command again.
INTERRUPTIBLE_UNCONFIGUREDInterruptible onboarding is not enabled on this environment yet.
The command prints an HTTP error and stopsFetch the URL without the pipe, for example curl -sS "https://api.openrelay.inc/v1/provider/interruptible/bootstrap?token=otk_...". The response body names the error.
Node stays pending, then is removed at 10 minutesThe tunnel never came up. Check outbound UDP 443 to the gateway first, then UDP 4242 to the lighthouses. A market that blocks outbound UDP cannot run Interruptible nodes.
Node flips offline and backThe tunnel dropped and reconnected. Harmless unless it stays offline for 10 minutes, which removes the node.

On this page