Breaking changes and migration steps for existing deployments. Entries are newest-first.
When deploying to a VPS that was set up before a breaking change, follow the Migration steps for each entry between the deployed version and the current version.
Vixie cron (Ubuntu default) ignores CRON_TZ in /etc/cron.d/ files, causing all timezone-aware schedules to run in UTC instead of the configured timezone. cronie is a drop-in replacement with native CRON_TZ support and automatic DST handling. Also adds configurable stack.openclaw.auto_update_time schedule and comprehensive timezone abbreviation support (70+ abbreviations covering Americas, Europe, Asia, Oceania, Africa). Full IANA timezone names are also accepted (e.g. Asia/Tokyo).
What changed:
playbooks/02-base-setup.md: addedcronieto apt install list.claude/skills/setup-vps/SKILL.md: addedcronieto apt installbuild/tz-abbreviations.mjs: new file — comprehensive TZ abbreviation → IANA mappingbuild/parse-schedule-time.mjs: new file — parses"9:30 AM PST"or"9:30 AM Asia/Tokyo"to cron expressionsbuild/pre-deploy.mjs: imports new modules, parsesstack.openclaw.auto_update_time, resolves{{CRON_TZ_LINE}}in cron templatesstack.yml.example: addedstack.openclaw.auto_update_timescheduledeploy/host/register-cron-jobs.sh: auto-update cron uses configurable time +CRON_TZ, all CRON_TZ emission is conditionalplaybooks/08c-deploy-report.md: fixed staleHOSTALERT_DAILY_REPORT_TIMEreferences
Migration:
-
Install cronie (replaces Vixie cron, preserves existing cron files):
sudo apt install -y cronie
-
Verify cronie is active:
dpkg -l cronie | grep '^ii' && systemctl status cronie
-
Add
stack.openclaw.auto_update_timetostack.yml(optional — defaults to3:00 AM PST):stack: openclaw: auto_update_time: "3:00 AM PST"
-
Rebuild and deploy:
npm run pre-deploy scripts/sync-deploy.sh sudo bash <INSTALL_DIR>/host/register-cron-jobs.sh
The daily VPS status report cron job has been renamed and the sandbox bind mount path reverted from /tmp/.host-status to /workspace/.host-status. This avoids conflicts with OpenClaw's upstream "healthcheck" skill which was being incorrectly triggered by the old "Daily VPS Health Check" cron name and prompt.
What changed:
stack.yml.example:health_check_cronrenamed tostatus_report_cronbuild/pre-deploy.mjs: env varHEALTH_CHECK_CRONrenamed toSTATUS_REPORT_CRONdeploy/host/register-cron-jobs.sh: cron renamed from "Daily VPS Health Check" to "Daily VPS Status Report", prompt reworded to avoid "health"/"check" language, file paths updated to/workspace/.host-status/openclaw/*/openclaw.jsonc: bind mount reverted from/tmp/.host-statusto/workspace/.host-status, removeddangerouslyAllowReservedContainerTargets, keptdangerouslyAllowExternalBindSources
Migration:
-
Update
stack.yml— rename the toggle:defaults: status_report_cron: false # was: health_check_cron claws: personal-claw: status_report_cron: true # was: health_check_cron
-
Update
openclaw.jsoncfor each claw — change the sandbox docker bind: -
Rebuild and deploy:
npm run pre-deploy scripts/sync-deploy.sh --all --force
-
On the VPS, remove the old cron job and re-register:
# Remove old cron (run inside the claw container or via openclaw CLI): openclaw --instance personal-claw cron remove --name "Daily VPS Health Check" # Re-register cron jobs: sudo bash /home/<project>/openclaw/host/register-cron-jobs.sh
-
Restart the claw container to pick up the new bind mount:
sudo -u openclaw bash -c 'cd <INSTALL_DIR> && docker compose up -d --force-recreate'
BREAKING: In-container updates (ALLOW_OPENCLAW_UPDATES) are removed. Updates are now handled host-side by build-openclaw.sh. Each claw can run a different OpenClaw version via openclaw_version in stack.yml. .git is no longer included in the Docker image.
What changed:
build/pre-deploy.mjs: per-clawopenclaw_version+openclaw_image_tag, newSTACK__OPENCLAW_VERSIONSenv var, removedSTACK__STACK__IMAGE+allow_updatesdocker-compose.yml.hbs: image tag moved from anchor to per-claw block, removedALLOW_OPENCLAW_UPDATES+OPENCLAW_SYSTEMD_UNIT, addedOPENCLAW_ALLOW_INSECURE_PRIVATE_WS,SHELL,TERMdeploy/host/build-openclaw.sh: rewritten for multi-version support — loops over unique specifiers, dual-tags (mutable specifier + immutable version), version state filesdeploy/host/auto-update-openclaw.sh: new daily cron script — checks for new versions, rebuilds changed specifiers, recreates containersdeploy/host/register-cron-jobs.sh: added auto-update cron registrationdeploy/openclaw-stack/entrypoint.sh: removed section 1d (git/branch/exclude handling) — no-op without.gitstack.yml.example: addedauto_update: true, per-clawopenclaw_versionexample, removedallow_updates
Migration:
-
Update
stack.yml:stack: openclaw: version: stable # Stack-wide default auto_update: true # Enable daily host-side update check defaults: # Remove allow_updates entirely (delete the line) claws: personal-claw: # No openclaw_version → inherits stack.openclaw.version (stable) # To pin a claw: # work-claw: # openclaw_version: v2026.3.8
-
Rebuild and redeploy:
npm run pre-deploy && scripts/sync-deploy.sh --all --force sudo -u openclaw <INSTALL_DIR>/host/build-openclaw.sh sudo -u openclaw bash -c 'cd <INSTALL_DIR> && docker compose up -d' sudo bash <INSTALL_DIR>/host/register-cron-jobs.sh
Verify: docker images | grep openclaw- shows version-tagged images (e.g., :stable, :v2026.3.12). cat <INSTALL_DIR>/.openclaw-versions/stable shows resolved version. cat /etc/cron.d/openclaw-auto-update exists if auto-update enabled.
BREAKING: The openclaw-net Docker bridge subnet is now pinned to 10.200.0.0/24 (was auto-assigned, typically 172.x.0.0/24). Cloudflared reverted from network_mode: host to bridge networking with a static IP (10.200.0.100). Tunnel ingress routes must use Docker DNS container names (not localhost).
What changed:
docker-compose.yml.hbs: openclaw-net pinned to10.200.0.0/24, cloudflared on bridge withipv4_address: 10.200.0.100openclaw.jsonc(all configs):trustedProxieschanged to["10.200.0.100"]cf-tunnel-setup.sh: generates routes using Docker DNS names (e.g.,http://<project>-openclaw-<claw>:18789) instead oflocalhost- Cloudflared hardened:
cap_drop: [ALL],no-new-privileges,read_only
Migration:
-
Update
trustedProxiesin each claw'sopenclaw.jsonc(live on VPS or local per-claw config):"trustedProxies": ["10.200.0.100"]
-
Update Cloudflare tunnel ingress routes to use Docker DNS names instead of
localhost. Either:- Run
scripts/cf-tunnel-setup.sh setup-routes(requiresCF_API_TOKEN), or - Manually update in Cloudflare Dashboard: change
http://localhost:<port>tohttp://<project>-openclaw-<claw>:<port>for each route
- Run
-
Recreate containers (network subnet change requires
down+up):sudo -u openclaw bash -c 'cd <INSTALL_DIR> && docker compose down && docker compose up -d'
Verify: sudo docker network inspect openclaw_openclaw-net --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' should return 10.200.0.0/24. sudo docker inspect <project>-cloudflared --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' should return 10.200.0.100.
BREAKING: Renamed LLMTERY_* env vars and config keys to LLMETRY_* (typo fix).
Migration: Update .env and stack.yml if you have the old LLMTERY_ spelling. npm run pre-deploy will use the corrected names.