# Houdini image — design notes and debugging history Context for whoever (Claude) touches this Dockerfile next. Simon doesn't need to read this — it's implementation reasoning, not usage docs (those are in `README.md`). ## Why these choices - **Base image + install command copy SideFX's own `docker/Ubuntu/Dockerfile`** (shipped inside the Houdini tarball under `docker/Ubuntu/`), not guessed. That reference folder has been deleted from this repo once its contents were folded in here — it only ever served as a source to copy from, and keeping it around invited drift between two copies of the same info. - **Ubuntu 22.04, not Rocky/RHEL.** The tarball is `gccXX.X`-tagged (here `gcc14.2`), meaning the runtime needs a `libstdc++` new enough for that build. Ubuntu's base image keeps `libstdc++6` updated via regular package updates; RHEL-family distros pin their system toolchain for the whole OS lifetime, so a Rocky 9 base risks a `GLIBCXX_x` version mismatch depending on which Houdini build you're on. This was a real, not hypothetical, concern with the 22.0.429/gcc14.2 build tested here. - **`--auto-install --accept-EULA `, no `--make-dir`, no `--install-license`.** An earlier draft of this Dockerfile invented an `--install-license ` flag that doesn't exist, based on scripted- install docs that actually describe a *different* tool (`houdini_installer`, the newer credential-based launcher — irrelevant here, since it needs network + stored SideFX credentials at build time, which is worse for a private-registry-baked-tarball workflow). The flags actually used are copy-pasted from SideFX's own Dockerfile. - **EULA date is a plain `YYYY-MM-DD`, no `SideFX-` prefix.** The `SideFX-YYYY-MM-DD` form belongs to `houdini_installer`'s config file, not to `houdini.install`. Mixing the two up silently breaks `--accept-EULA` matching. Value as of writing: `2021-10-13` (from https://www.sidefx.com/legal/license-agreement/ — SideFX can update this without notice, it's not pinned/checked anywhere). - **`/opt/hfs` symlink is created explicitly.** `houdini.install`'s default behavior only creates a major.minor symlink (`/opt/hfs22.0` here), not a generic `/opt/hfs`. The Dockerfile globs for the installed `hfs[0-9]*` dir and symlinks it to `/opt/hfs` itself, so `houdini-run` and the `/etc/profile.d` snippet don't need to know the exact version. ## Runtime library list SideFX's own package list in `docker/Ubuntu/Dockerfile` is built for headless `hython`/`sesinetd` use (their `docker-compose.yml` only ever runs `hython` or `sesinetd` as separate services, never the GUI). Running the actual `houdinifx` GUI needed 4 more libs, each found by `ldd`-checking the real binaries/plugins in a test build and confirmed missing before adding: - `libegl1` — EGL dispatch, `houdinifx-bin` linked against it directly. - `libatomic1` — `houdinifx-bin`/`hython-bin` both needed it. - `libxkbfile1` — same. - `libxcb-cursor0` — this one took the longest to find. Qt6's `xcb` platform plugin (`libqxcb.so`, under `/opt/hfs/dsolib/Qt_plugins/platforms/`) hard-depends on it (a known Qt6 change from Qt5, where cursor theming moved from Xcursor to xcb-cursor). Without it, Houdini fails at Qt platform-plugin init with a generic "no Qt platform plugin could be initialized" — nothing points at the actual missing symbol unless you `ldd` the plugin directly. If a future Houdini/Qt version adds new failures here, the fastest diagnosis path is: `ldd` every `.so` under `dsolib/Qt_plugins/` recursively (see git history of this file / session transcript for the exact one-liner), not guessing from the vague top-level Qt error message. ## `houdini-run` strict-mode compatibility `houdini_setup_bash` (sourced by `houdini-run` to set up `$HFS`/`PATH`/etc.) is not written for `set -euo pipefail`: - It references variables that are legitimately unset in some code paths (`set -u` would abort). - It does things like `d=$(which java)` expecting a silent empty result on failure (`set -e` would abort on the failed `which`). Fix: wrap the `source ./houdini_setup` call with `set +euo pipefail` / `set -euo pipefail` around it, rather than trying to patch the vendor script. ## NixOS / distrobox host environment leakage Distrobox forwards the *host* shell's environment into the container by design (needed for `DISPLAY`, `WAYLAND_DISPLAY`, `XDG_*`, D-Bus, etc. to work at all for GUI apps). On a NixOS host that also sets its own `LD_LIBRARY_PATH` (observed here pointing at a Nix-store `alsa-lib` built against a much newer glibc than Ubuntu 22.04 ships), that leaks into the container and crashes Houdini's binaries at dynamic-link time (`GLIBC_ABI_GNU2_TLS not found`, etc.) — a library meant for the host's libc, loaded into a binary linked against the container's older libc. `houdini_setup_bash` only ever *prepends* to `LD_LIBRARY_PATH` if it's already set — it never resets it. So `houdini-run` explicitly `unset LD_LIBRARY_PATH` before sourcing it, discarding whatever leaked in from the host and letting Houdini build its own from scratch. This is safe on any host, not just NixOS — it just happens to matter there. This class of problem (host env leaking into the container and colliding with container-local binaries) is worth checking first if a *new*, unexplained crash shows up after this image has otherwise worked before. ## sesinetd / Apprentice licensing — the two real bugs Both now handled automatically inside `houdini-run` (not just documented as manual steps), found via `/var/log/sidefx/sesinetd.log` after a genuinely confusing symptom in the GUI: 1. **License file permissions.** `sesinetd` drops privileges to `www-data` after being started as root via `sudo`. The license file bind-mounted from the host (`~/.houdini-apprentice/licenses:/usr/lib/sesi/licenses`) is created with `touch` as the host user, so it's not writable by `www-data` inside the container. Result: activation's network round-trip to SideFX succeeds fine (curl-level success), but writing the resulting license locally fails — surfaced in the GUI as the deeply misleading **"Success (curl error 0)"** error dialog that just loops back to the install-license prompt with no indication anything is actually wrong. The real error only shows up in `/var/log/sidefx/sesinetd.log`: `[FATAL - Licensing] [Manager] - Failed to open license file.` Fix: `houdini-run` now `chmod 666`s the license file before starting `sesinetd`, every time, unconditionally. 2. **Stuck self-signed TLS cert.** If cert generation for `sesinetd`'s HTTPS listener gets interrupted (observed here across our own repeated container kill/restart cycles during testing), it leaves 0-byte `/usr/lib/sesi/ssl/auth.cert` / `auth.priv` behind. On next start, `sesinetd` tries to read those, fails (`Bad certificate chain: 'PEM lib'`), and — rather than regenerating — spins retrying at ~100% CPU forever, never actually finishing startup or opening port 1715. No error surfaces in the GUI for this one at all; the only symptom is a pegged CPU core and a license dialog that never succeeds. Fix: `houdini-run` deletes those two files first if either is 0 bytes. Also confirmed, contra a plausible-sounding but wrong worry: SideFX's own docker README warns that regular (non-floating) licenses need special "container enabled" entitlements to run `sesinetd` in Docker, working around it with `network_mode: host` in their own `docker-compose.yml`. That warning is about Docker's default *isolated* network namespace confusing the node-lock check. Distrobox shares the host's network namespace by default (no `--unshare-netns`), which is the same situation as SideFX's own workaround — so this doesn't apply here, and was confirmed by an actual successful Apprentice activation. ## Confidence table (as of the last real end-to-end test) Everything below was confirmed with an actual build + an actual `distrobox create` + `houdini-run` launch on a real AMD iGPU (Vega, `raven`) host, not just read from docs. | Area | Confidence | Note | |---|---|---| | Base image + install command | High | Matches SideFX's own Dockerfile; built end to end against the real 22.0.429/gcc14.2 tarball. | | `/opt/hfs` symlink | High | Confirmed by test build. | | EULA date value | Medium | `2021-10-13` as of writing; unpinned, SideFX can change it. | | Licensing works under distrobox's shared netns | High | Confirmed with a real Apprentice activation. | | Runtime library list | High | Vendor's list + 4 libs added, all confirmed via `ldd` and an actual successful launch. | | `houdini-run` strict-mode compatibility | High | Confirmed end to end. | | `sesinetd` license-file-permission and stuck-cert fixes | High | Both bugs actually hit during testing, root-caused via `/var/log/sidefx/sesinetd.log`, fixed in `houdini-run`, confirmed working after the fix. | | distrobox default hostname | Medium | Pinned explicitly (`houdini-apprentice`) rather than trusting the default, since Apprentice node-locks to hostname too. | | GUI rendering via XWayland (Hyprland host) | High | Confirmed real AMD hardware acceleration (`glxinfo`: direct rendering, AMD Radeon Vega renderer, not llvmpipe). First launch was slow (container's first-run package install + cold start); later launches fluid. | | AMD OpenCL (sims) | Low | Not set up; needs ROCm/rusticl. GL/Vulkan viewport is unaffected. | | NVIDIA variant | Untested | Builds fine (same base, no extra packages), but never run against real NVIDIA hardware — only the `amd` target has been used end to end. | ## Repo-wide conventions See the top-level `../README.md` for the multi-software repo convention (one self-contained folder per app, shared registry setup notes) — that file is meant for Simon to read; this one and the top-level `../CLAUDE.md` (if one exists) are for future-Claude context.