Sign inSign up

nicholasyue/shader-noodles

By nicholasyue

•Updated 2 days ago

Image
0

566

nicholasyue/shader-noodles repository overview

⁠shader-noodles

A browser-native MaterialX graph editor — build and edit MaterialX shading graphs in a browser tab, with a live rendered preview. This image is the application, already behind a web server, ready to run.

It is a static site: no server-side application, no database, no state, and nothing to configure.

⁠Quick start

docker run -d --name shader-noodles -p 8080:8080 \
    nicholasyue/shader-noodles:0.3.0

Open http://127.0.0.1:8080/⁠.

Use 127.0.0.1, not localhost — the port is published on IPv4, while localhost resolves to the IPv6 ::1 first on many Linux systems, which some clients will not fall back from.

Pick a material from the menu at the bottom left (marble solid and simple hair have real graphs to explore), drag a node to move it, drag between ports to connect them, and click a node to edit its parameters. The preview is the material itself, generated and rendered live as you edit.

Alongside MaterialX's own examples is lg parquet plank, an oak parquet floor translated from Larry Gritz's 1995 RenderMan shader parquet_plank.sl into a MaterialX graph — planks, grooves, growth rings and grain, each with its own parameter. The pattern follows the original; its lighting is MaterialX's standard surface rather than RenderMan's plastic. View it on the grid geometry.

Judge it on real geometry. The render view offers a sphere, a grid, a cylinder, a torus and the standard shader ball, each carrying uvs and a proper tangent frame, so anisotropy and normal maps behave as they should rather than as a sphere's parameterisation flatters them. Navigation is what a DCC user expects — Alt+LMB tumble, Alt+MMB pan, Alt+RMB dolly, wheel to zoom, F to frame — and a spin toggle turns the model for judging a highlight in motion. Framing is computed from each geometry's own bounds, so switching never leaves you looking at the inside of something.

⁠Requirements

  • A browser with WebGL2 — any current Chrome, Firefox, Edge or Safari.
  • Nothing else. No plugins, no runtime, no configuration.

"Current browser" is not quite sufficient, and the exception is worth knowing before you field the report: a browser can decline to give a page WebGL2 even on a machine whose graphics are working, if its own GPU process is unhealthy — Chromium-family browsers no longer fall back to software rendering for WebGL on their own. The app now says so on its start-up screen instead of appearing to load forever, and names what to check. If you hit it, chrome://gpu is the first stop, and a different browser is often the fastest answer, since Firefox uses an independent graphics stack.

⁠What you should know before deploying it

Saving depends on the URL you serve it from. The editor can save a .mtlx file straight back over the file you opened, but browsers only permit that in a secure context — https://…, http://localhost, or http://127.0.0.1. Over plain http:// to a hostname or LAN address, Save downloads a copy into the Downloads folder instead. Nothing is lost and the app says so in its status line, but it surprises people. Put it behind TLS for any shared deployment.

The image serves plain HTTP on 8080 and terminates nothing by default, on the assumption that whatever you run it in already has a TLS terminator you manage. If it does not, there are two supported routes and neither needs a rebuild: mount your own certificate and a copy of /etc/nginx/tls.conf.example at /etc/nginx/tls, and a TLS listener appears on 8443; or, if you have no certificate at all, use the Caddy compose file in the documentation, which obtains and renews one for you. No certificate ships in this image and none is generated — a key inside a public image is a key everyone who pulls it holds, and a self-signed certificate makes browsers warn harder rather than less.

It reaches nothing. No telemetry, no analytics, no update check, no external asset fetch, no absolute URLs anywhere in the bundle. Documents are read and written in the browser, never uploaded. It runs air-gapped, and it will not phone home from inside your network.

First load is ~8 MB (11 MB uncompressed, precompressed here at gzip -9 and served with gzip_static). Most of that is the MaterialX standard library and the bundled demo materials. There is a progress indicator; it takes a moment.

The shader ball is a further ~3.6 MB, fetched only when you select it. It is deliberately not in the initial payload — most sessions never open the geometry menu, and carrying it for everyone would make the first load worse for the majority to save one fetch for the minority. Expect a short pause the first time you pick it, once per browser cache.

⁠Running it locked down

The image is built to run restricted, and this is the configuration that is tested before each release:

docker run -d --name shader-noodles -p 8080:8080 \
    --read-only --tmpfs /tmp \
    nicholasyue/shader-noodles:0.3.0

--tmpfs /tmp is required alongside --read-only: the web server needs somewhere for its scratch paths, and every one of them is under /tmp by design so that nothing else needs to be writable.

runs asuid 101, non-root, declared numerically for runAsNonRoot
listens on8080 (unprivileged)
healthGET /healthz → 200, and a HEALTHCHECK is set
base imagenginx:1.27-alpine
size~76 MB
platformslinux/amd64, linux/arm64

Serving is already configured correctly — .wasm as application/wasm, precompressed assets, and Cache-Control: no-cache so that deploying a new tag is actually picked up by browsers rather than silently served from cache.

⁠Kubernetes

A Deployment and a Service; no operator or chart is needed.

containers:
  - name: shader-noodles
    image: nicholasyue/shader-noodles:0.3.0
    ports: [{ containerPort: 8080 }]
    readinessProbe: { httpGet: { path: /healthz, port: 8080 } }
    livenessProbe:  { httpGet: { path: /healthz, port: 8080 } }
    securityContext:
      runAsNonRoot: true
      runAsUser: 101
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
      capabilities: { drop: ["ALL"] }
    volumeMounts: [{ name: tmp, mountPath: /tmp }]
volumes:
  - name: tmp
    emptyDir: {}

It is stateless, so replicas scale freely and any pod can be killed at any time. The only state a user has lives in their own browser.

⁠Tags

  • :<version> — an exact build, e.g. :0.3.0. Pin this in production.
  • :latest — the most recent release.

A published version tag is never re-pointed at different contents: :0.1.0 is still the build it always was, without the render view. New features arrive as a new version, so a pin stays worth having.

Every image records exactly which build it is:

docker image inspect nicholasyue/shader-noodles:0.3.0 \
    --format '{{index .Labels "dev.shader-noodles.build-id"}}'
# v0.3.0 (7dbe9af)

The same identifier appears in the application's status bar, so a screenshot identifies its own build. Standard OCI labels (version, revision, created, licenses, vendor) are set too.

⁠Current limitations

Recorded rather than hidden — these are known, not undiscovered:

  • No texture or image support. Materials using <image> nodes will not render correctly. The bundled demo materials are all procedural.
  • The colour picker's sRGB↔linear conversion is a 2.2 gamma approximation, not the exact transform.
  • IPv6-only deployment is not supported; the server listens on IPv4.
  • The image pins its nginx base for reproducibility, which means base-image security fixes arrive by a new release, not automatically.

⁠Reporting a problem

In the app, press About (right of the status bar) then Copy details — that captures the build id, the MaterialX and Noodles versions, and which graphics driver answered, which settles most "it will not render" reports on its own. For anything involving clicking or dragging, add ?debug=1 to the URL first, then use the Diagnostics panel at the bottom: Clear, reproduce once, Copy.

If it never gets as far as the editor, the start-up screen itself reports the reason and offers a Show diagnostics button that opens the same panel. The whole start-up log is also written to the browser's developer console, so it can be copied even from a session that got no further.

⁠Licence — please read

This software is proprietary. Copyright © 2026 Nicholas Yue, all rights reserved. Pulling this image does not grant a licence to use, copy, modify, distribute or create derivative works from it. Possession is not permission — use requires a separate written agreement with the copyright holder.

The open-source components built into it (MaterialX, Noodles, Emscripten, RapidJSON, stb, and the bundled fonts) remain under their own licences and are unaffected by the above. Both texts ship inside the image and are served, so you can read them from a running container without unpacking anything:

curl http://127.0.0.1:8080/LICENSE
curl http://127.0.0.1:8080/THIRD-PARTY-NOTICES.txt

Tag summary

Content type

Image

Digest

sha256:4089360a3…

Size

42.1 MB

Last updated

2 days ago

docker pull nicholasyue/shader-noodles