After every rebuild the freshly-baked dashboard bundle lives in /opt/dashcaddy/status/dist/ + sw.js. DNS2 also serves files from /var/www/dashcaddy-status/dist/ + sw.js (the original Windows-installer mirror path). Without an explicit copy step between build and start.sh, the served bundle stays on whatever hash was there before, while the API responds with new code. That mismatch is what shows up in the dashboard as "version unavailable" + "no data" widgets — saw it in the deploy that followed DC-046/047 (fixed by a manual cp this time, never again). Sync block runs before docker run: cp /opt/dashcaddy/status/dist/*.js /var/www/dashcaddy-status/dist/ cp /opt/dashcaddy/status/sw.js /var/www/dashcaddy-status/ cp /opt/dashcaddy/status/index.html /var/www/dashcaddy-status/ All guarded so set -e doesn't kill the container start on a single per-file failure (e.g. read-only mount, missing dir). Missing source dir is a WARN + no-op rather than a fatal — fresh installs without status/dist/ don't get a stale-bundle problem, just a log line. Test: scripts/test-start-sh-sync.sh — 7 assertions across 4 cases (fresh-copy, missing-source, idempotent-re-sync, set-e-survives-permission- denied). All pass.