What happened?
On a self-hosted Docker deployment, every bundled plugin card in the home gallery renders as a live, scaled iframe. The baked poster + hover-clip path never activates, even though the shipped manifest has bakes for 176 of them.
The cause is that deploy/Dockerfile never copies data/ into the image, so data/plugin-previews/manifest.json — the file CI commits precisely so that deployments can find the bakes — is absent at runtime.
$ docker exec <daemon> ls /app/data/plugin-previews
ls: /app/data/plugin-previews: No such file or directory
The chain from there is short and entirely silent:
resolvePluginPreviewsDir() defaults to <projectRoot>/data/plugin-previews;
loadManifest() finds no manifest.json and returns {};
applyBakedPreviews() early-returns the records untouched, so no record gets an od.bakedPreview;
inferPluginPreview(record, { preferBaked: true }) therefore always falls through to the live-HTML branch.
Measured on 0.19.2 with 461 registered plugins: manifest.json in the repo has 176 entries, and 0 records carried od.bakedPreview. After making the directory available, the same daemon reports 176 — deck 79, prototype 79, video 10, scenario 4, image 1, audio 1, template 2.
deploy/Dockerfile copies skills, design-systems, craft, prompt-templates, assets, and plugins/_official; data/ is the one content directory missing from that list, which reads more like an oversight than a decision.
Why this may not be visible upstream
The packaged desktop app gets the repository contents through a different path, so it finds the manifest and uses the bakes. Only Docker/self-hosted deployments are affected — and there the degradation is invisible: the gallery still renders, just via the fallback. Nothing is logged.
That last part looks worth fixing on its own. loadManifest() already carries this comment for the malformed case:
// A malformed/unreadable manifest would otherwise silently disable every baked preview with no trace; surface it so it's diagnosable.
…and warns accordingly. The absent case returns {} with no message, which is the same failure with no trace — exactly what that comment set out to prevent.
Steps to reproduce
- Build the image from
deploy/Dockerfile (or run any published Docker/self-hosted deployment).
- Open the home gallery.
- Inspect any bundled deck or prototype card: it is an
<iframe class="plugins-home__html-iframe">, never the baked MediaSurface poster + <video>.
- Confirm the cause:
docker exec <daemon> ls /app/data/plugin-previews → no such directory.
- Optional cross-check via the API: every record's
manifest.od.bakedPreview is undefined.
Expected behavior
A Docker/self-hosted deployment should find the checked-in data/plugin-previews/manifest.json and serve baked previews for the 176 plugins that have them, the same as the packaged app.
Suggested fix — data/ alongside the other content directories in both stages of deploy/Dockerfile:
COPY data ./data
...
COPY --from=build --chown=open-design:open-design /app/data ./data
Narrowing it to data/plugin-previews would work equally well if data/ holds anything that should stay out of the image.
Separately, and independent of the above: consider a one-line notice when the manifest is absent, so a deployment that silently loses all 176 bakes is diagnosable rather than merely slower.
Impact
- Cost.
plugin-preview-bakes.ts opens by noting the live path "is GPU-expensive at scale", which is the reason bakes exist. Self-hosted deployments currently pay that for the entire gallery — 461 live iframes where 176 would have been a poster and a small clip.
- It routes every card onto a path that is otherwise rarely exercised. Filed separately as the deck thumbnails landing outside their card, which is only reachable this way — deck happens to be the best-baked category (79/80), so with bakes present those tiles never take the buggy branch.
Open Design version
0.19.2
Platform
Linux (Docker, self-hosted)
Additional context
Found while chasing blank thumbnails in the home rail on a self-hosted deployment. Happy to open a PR for the Dockerfile change, the absent-manifest notice, or both.
What happened?
On a self-hosted Docker deployment, every bundled plugin card in the home gallery renders as a live, scaled iframe. The baked poster + hover-clip path never activates, even though the shipped manifest has bakes for 176 of them.
The cause is that
deploy/Dockerfilenever copiesdata/into the image, sodata/plugin-previews/manifest.json— the file CI commits precisely so that deployments can find the bakes — is absent at runtime.The chain from there is short and entirely silent:
resolvePluginPreviewsDir()defaults to<projectRoot>/data/plugin-previews;loadManifest()finds nomanifest.jsonand returns{};applyBakedPreviews()early-returns the records untouched, so no record gets anod.bakedPreview;inferPluginPreview(record, { preferBaked: true })therefore always falls through to the live-HTML branch.Measured on 0.19.2 with 461 registered plugins:
manifest.jsonin the repo has 176 entries, and 0 records carriedod.bakedPreview. After making the directory available, the same daemon reports 176 — deck 79, prototype 79, video 10, scenario 4, image 1, audio 1, template 2.deploy/Dockerfilecopiesskills,design-systems,craft,prompt-templates,assets, andplugins/_official;data/is the one content directory missing from that list, which reads more like an oversight than a decision.Why this may not be visible upstream
The packaged desktop app gets the repository contents through a different path, so it finds the manifest and uses the bakes. Only Docker/self-hosted deployments are affected — and there the degradation is invisible: the gallery still renders, just via the fallback. Nothing is logged.
That last part looks worth fixing on its own.
loadManifest()already carries this comment for the malformed case:…and warns accordingly. The absent case returns
{}with no message, which is the same failure with no trace — exactly what that comment set out to prevent.Steps to reproduce
deploy/Dockerfile(or run any published Docker/self-hosted deployment).<iframe class="plugins-home__html-iframe">, never the bakedMediaSurfaceposter +<video>.docker exec <daemon> ls /app/data/plugin-previews→ no such directory.manifest.od.bakedPreviewis undefined.Expected behavior
A Docker/self-hosted deployment should find the checked-in
data/plugin-previews/manifest.jsonand serve baked previews for the 176 plugins that have them, the same as the packaged app.Suggested fix —
data/alongside the other content directories in both stages ofdeploy/Dockerfile:Narrowing it to
data/plugin-previewswould work equally well ifdata/holds anything that should stay out of the image.Separately, and independent of the above: consider a one-line notice when the manifest is absent, so a deployment that silently loses all 176 bakes is diagnosable rather than merely slower.
Impact
plugin-preview-bakes.tsopens by noting the live path "is GPU-expensive at scale", which is the reason bakes exist. Self-hosted deployments currently pay that for the entire gallery — 461 live iframes where 176 would have been a poster and a small clip.Open Design version
0.19.2
Platform
Linux (Docker, self-hosted)
Additional context
Found while chasing blank thumbnails in the home rail on a self-hosted deployment. Happy to open a PR for the
Dockerfilechange, the absent-manifest notice, or both.