# LAN render farm

> Render in the background on the other machines of your LAN: start a worker, pair it with a code, tick it, and pick Farm in the Render dialog. No farm manager, no licence per machine.

Oink has a small render farm built in. Other machines on your network run a **worker**. The app finds them by itself, you pair each one once with a code, and background renders can then be split over this machine and the workers. Nuke has nothing like this without a farm manager and a render licence per machine.

For Deadline, AWS Deadline Cloud, OpenCue or Flamenco, see [Render farms](/docs/nuke-users/render-farms).

## What you need

1. **Shared storage.** Every machine must be able to read the comp and its input files, and write the output folders: a NAS mounted on each machine.
   - The mount can sit at a different place on each machine (see [Path variables](#path-variables)).
   - When you render on the farm, the app saves a frozen copy of the comp in a `<codename>_farm` folder next to your comp, so the workers can read it. Save your comp on the shared storage first.
2. **The same install on each worker.** A copy of the Oink folder, installed with `python3 install.py` as on your own machine (later: a package). Add Blender in `blender/` only if your comps need it. Use the same engine, the same engine version (Blender version, if you use Blender) and the same Oink version everywhere: the app will not send work to a machine with another engine version, or with other Oink code (another version can render other pixels, and one sequence would then flicker between machines). Update a worker with `git pull` (or a new copy) and restart it.
3. **One network.** Workers are found by a broadcast on the local network. For a machine on another subnet, use **Add Machine...** (see below).

## Start a worker

On the other machine, in the Oink folder:

```
./render.sh --worker
```

On Windows: `render.bat --worker`. The worker prints something like:

```
19:18:19 render worker 'studio-2' (id debc6a0d9e18c39d) listening on 192.168.1.20:47471
19:18:19 Pairing code: 133 293   (Background Renders > Workers > Pair; valid 10 min)
19:18:21 [worker] engine ready: GPU: NVIDIA GeForce RTX 4090
```

It keeps one engine running between chunks, so a chunk starts with no start-up delay.

Options (they are remembered for the next start):

| Option | What it does |
|---|---|
| `--name NAME` | the name shown in the app (default: the host name) |
| `--cpu` | render with no GPU (Mesa lavapipe, needs `mesa-vulkan-drivers`): slower, same pixels |
| `--gpu N` | use GPU number N |
| `--only-when-idle [MIN]` | take work only when nobody has used this machine's keyboard or mouse for MIN minutes (default 10). If the user comes back, the worker finishes the current frame and hands the rest back. `--always` turns it off. |
| `--max-share PCT` | use at most this share of the machine: CPU threads, GPU memory (of the GPU budget) and memory |
| `--path-var jobs=/mnt/jobs` | where a shared folder is on this machine (repeatable) |
| `--port N`, `--bind ADDR` | the TCP port (default 47471) and the address to listen on (default: this machine's LAN address) |
| `--beacon-to HOST[:PORT]` | also announce this worker straight to a machine on another subnet (repeatable), where broadcasts do not reach |
| `--localize`, `--no-localize` | read the local copies made by Localization instead of the network files (remembered; default: read the sources). `--use-local-file-cache` works too |
| `--state-dir DIR` | where the worker keeps its pairings and settings |
| `--new-code`, `--list-pairings`, `--unpair ID\|all` | pairing housekeeping |

Only-when-idle reads GNOME's idle time or systemd-logind on Linux, the HID idle time on macOS and the last input time on Windows. Where none of these exist the worker says so and renders whenever asked.

The worker takes the machine's GPU lease for every chunk, like the app's AI jobs. So on a machine that is also running Oink AI jobs, renders and AI take turns instead of running the GPU out of memory. Programs that don't know the lease (the machine user's own render, a game) are seen by the GPU memory they use: when less than 2 GB is free, the worker hands the chunk back and is asked again later.

To run it at every boot, start `render.sh --worker` from a systemd user service, a macOS launch agent or a Windows scheduled task, as an ordinary user.

## Pair it and render

1. In the app, open **Background Renders > Workers**. Your own machine is the first row; the worker appears within a few seconds with the status **Pair...**.
2. Click **Pair...**, type the 6-digit code the worker printed, press Enter. The row turns to **Idle** and its **Use** box is ticked.
3. Untick **Use** on any machine you don't want to render on, including this one.
4. Render as usual (**Render > Render All**, F5 / F7, or a Write's **Render** button). Tick **Render in background**, set **Render on** to **Farm**, press OK.

## Watch it

- The **Renders** tab shows one row per Write, as for local background renders. **Render On** says **Farm** and how many machines took part. The progress bar is split into chunks: orange when done, darker orange while rendering, red when failed.
- Click a farm row to see its chunk list: frames, the machine that rendered them, status, frames done and tries. Hover a chunk for its history (which machine tried it, how long it took, the error).
- The **Workers** tab shows each machine's renderer (**GPU: ...** or **CPU: ...**, CPU in amber), status (Idle, Rendering, Waiting for GPU, User active, Offline, Not paired), the chunk it renders and its speed.
- The **Logs** tab shows every frame with the machine that wrote it: `[studio-2] Writing /mnt/jobs/sh010/out.0012.exr took 0.84 seconds`.

## What happens when something goes wrong

| Problem | What the farm does |
|---|---|
| A frame fails, or a worker crashes | the chunk's remaining frames go to a different machine; after 3 tries they are marked failed and the render ends with **Error** |
| A worker is switched off or its network drops | no heartbeat for 15 s (or the connection closes): its chunk is sent elsewhere |
| A machine fails 3 chunks of one render | it gets no more chunks of that render |
| A worker cannot see the comp or its input files | the render shows a warning naming the machine and the missing path, and the machine is left out of that render |
| A precomp | a Write that reads another Write's output starts only when that Write has finished. If the precomp fails, the Write reading it is not rendered. |
| The worker's user comes back (only when idle) | the worker finishes its current frame; the rest of the chunk goes back in the queue (not counted as a failure) |

Every frame is read back after it is written, as with `render.sh`, so a truncated file counts as a failed frame.

**Pause** stops handing out new chunks: running chunks finish. **Cancel** stops the running chunks after their current frame.

## Path variables

When the shared folder is mounted at different places, give it a name on each machine (as in Flamenco):

- On the worker: `render.sh --worker --path-var jobs=Z:/jobs` (Windows) or `--path-var jobs=/Volumes/jobs` (macOS).
- In the app: set `OPENCOMP_FARM_PATH_VARS="jobs=/mnt/jobs"` before starting it, or add `"path_vars": {"jobs": "/mnt/jobs"}` to `~/.config/<codename>/farm/controller.json`. Separate several variables with `;`.

For each worker, the app turns the variables both sides know into [path mapping rules](/docs/nuke-users/command-line-render) for the comp's own path and every Read and Write path in it. Hover a worker in the Workers tab to see the variables it knows.

## Machines on another network

Broadcasts don't cross routers. Click **Add Machine...** at the bottom of the Workers tab and type `host` or `host:port`, then pair it as usual. The app then asks it for its status every few seconds.

## Frame checks

Every still is first written under a temporary name and renamed when the file closes cleanly, so nobody ever reads half a frame. After writing, each frame is checked for NaN pixels, infinite pixels and an all-black image. A problem is a **warning** on the job (and a `WARNING:` line in `render.sh`), not a failure, because a black slate frame can be legitimate.

## Splitting one Cycles frame over machines

For a Cycles scene, the farm can also split the samples of one frame: every machine renders the whole frame with its own share of the samples, and the pictures are averaged. In the test (two machines, 4096 samples, 960x540) the merged picture matched a single 4096-sample render, and the wall time fell from about 11 s to about 8 s, because each machine still pays its own start-up and scene load. This works through the farm's protocol and the test tools; it is **not in the Render dialog yet**.

## Security

- **Pairing:** the worker accepts work only from apps it has been paired with. The code is used once and never sent over the network: both sides prove they know it with a password-authenticated key exchange (SPAKE2) and derive a shared secret, stored on both machines readable only by your user. After 3 wrong codes the worker makes a new one. Pairings are kept until you unpair (right-click a worker > **Unpair**, or `render.sh --worker --unpair all` on the worker).
- **Every message** is signed with that secret (HMAC-SHA256) and carries a timestamp and a one-time number. The worker refuses unsigned or altered messages, replays, messages for another machine and clocks more than 2 minutes apart (keep the clocks in sync).
- **Only renders:** a paired app can ask a worker for exactly one thing: render these frames of this comp (`.oink` or `.nk`) with these options. There is no way to run a command.
- **LAN only:** the worker listens on its LAN address only and refuses connections from outside private address ranges.
- **Not encrypted:** comp paths and frame numbers can be seen on the network.

**Trust model.** A paired app can make the worker read any comp and write any file that the worker's user can access: a comp's Write nodes choose where files go, and comps can run Python expressions. Pair only apps you trust, and run the worker as an ordinary user, not as root or an administrator.

## Ports

| Port | Protocol | Used for |
|---|---|---|
| 47470 | UDP broadcast | workers announce themselves every 2 s |
| 47471 | TCP | pairing and render requests to a worker |

Allow UDP 47470 in on the machines that run the app, and TCP 47471 in on the workers, for the LAN only. `OPENCOMP_FARM_DISCOVERY_PORT` and `OPENCOMP_FARM_WORKER_PORT` change them (`--port` on the worker).

On Rocky / RHEL / Fedora (firewalld; replace `10.10.21.0/24` with your LAN):

```
# the machine running the app: hear the workers
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="10.10.21.0/24" port port="47470" protocol="udp" accept'
# each worker: accept render requests
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="10.10.21.0/24" port port="47471" protocol="tcp" accept'
sudo firewall-cmd --reload
```

To undo, run the same commands with `--remove-rich-rule` instead of `--add-rich-rule`. With the ports closed the worker still starts, but the app never sees it (no beacon) or cannot reach it (**Add Machine...** fails with "No route to host").

On Windows, allow the engine's `python.exe` (and `blender\python.exe` if you use Blender) when Windows asks, for private networks only.


---
Sources: opencomp_server/farm_worker.py, opencomp_server/farm_controller.py, opencomp_server/farm_protocol.py, opencomp_server/bg_render_commands.py, opencomp_electron/src/components/render/BackgroundRendersPanel.tsx, opencomp_electron/src/components/render/RenderDialog.tsx, farm/README.md, docs/notes/p11-farm.md
