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.
What you need
- 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).
- When you render on the farm, the app saves a frozen copy of the comp in a
<codename>_farmfolder next to your comp, so the workers can read it. Save your comp on the shared storage first.
- The same install on each worker. A copy of the Oink folder, installed with
python3 install.pyas on your own machine (later: a package). Add Blender inblender/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 withgit pull(or a new copy) and restart it. - 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 --workerOn 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 4090It 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
- 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....
- Click Pair..., type the 6-digit code the worker printed, press Enter. The row turns to Idle and its Use box is ticked.
- Untick Use on any machine you don't want to render on, including this one.
- 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 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 allon 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 (
.oinkor.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 --reloadTo 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.