[{"data":1,"prerenderedAt":1261},["ShallowReactive",2],{"blog-list":3},[4,230,690,922,1165],{"id":5,"title":6,"body":7,"date":216,"description":217,"draft":218,"extension":219,"meta":220,"navigation":221,"ogImage":222,"path":223,"seo":224,"stem":225,"tags":226,"__hash__":229},"blog\u002Fblog\u002Fwatchtower-archived-what-to-do.md","Watchtower is archived: here's what to do now",{"type":8,"value":9,"toc":209},"minimark",[10,22,35,40,48,51,55,58,88,92,99,128,140,144,147,180,187,205],[11,12,13,14,21],"p",{},"If you run a homelab, you have probably leaned on ",[15,16,20],"a",{"href":17,"rel":18},"https:\u002F\u002Fgithub.com\u002Fcontainrrr\u002Fwatchtower",[19],"nofollow","Watchtower"," at some point. For the better part of a decade it was the default answer to \"how do I keep my containers up to date?\" That era is over.",[11,23,24,25,29,30,34],{},"On ",[26,27,28],"strong",{},"17 December 2025",", the maintainers archived ",[31,32,33],"code",{},"containrrr\u002Fwatchtower",". Archived means read-only: no more releases, no more fixes, no more security patches. And there is a second, more urgent problem.",[36,37,39],"h2",{"id":38},"why-it-doesnt-just-keep-working","Why it doesn't just keep working",[11,41,42,43,47],{},"Watchtower ships an ",[44,45,46],"em",{},"embedded"," Docker SDK pinned to API version 1.25. Docker Engine 29 and later require API 1.44 or newer. The two can no longer negotiate a common protocol, so on a current Docker host Watchtower simply fails to talk to the daemon. This isn't a slow deprecation you can ignore. Upgrade your Docker Engine and Watchtower stops working.",[11,49,50],{},"So \"do nothing\" has an expiry date attached to your next Docker upgrade.",[36,52,54],{"id":53},"your-options","Your options",[11,56,57],{},"There are a few honest paths forward:",[59,60,61,68,82],"ol",{},[62,63,64,67],"li",{},[26,65,66],{},"Pin Docker and freeze."," You can hold Docker Engine below 29 and keep the archived Watchtower limping along. This trades your container security posture for your update tool's, not a good trade for long.",[62,69,70,73,74,81],{},[26,71,72],{},"Use the community fork."," ",[15,75,78],{"href":76,"rel":77},"https:\u002F\u002Fgithub.com\u002Fnicholas-fedor\u002Fwatchtower",[19],[31,79,80],{},"nicholas-fedor\u002Fwatchtower"," is an active fork that keeps the original alive on modern Docker. If you want Watchtower's exact labels and behaviour with the least disruption, this is the lift-and-shift option. It's still the same Go codebase and the same safety model, though: a stop-gap, not a rethink.",[62,83,84,87],{},[26,85,86],{},"Move to a maintained successor."," Switch to a tool that's built for current Docker and adds the safety net Watchtower never had.",[36,89,91],{"id":90},"what-freshdock-changes","What freshdock changes",[11,93,94,98],{},[15,95,97],{"href":96},"\u002F","freshdock"," is a from-scratch successor written in Rust. It targets modern Docker (tested 24.x through 29+, auto-negotiated) and adds the thing that makes unattended updates actually safe:",[100,101,102,108,122],"ul",{},[62,103,104,107],{},[26,105,106],{},"Health-gated rollback."," A container counts as updated only after the new one passes its healthcheck, or stays up for a grace period if it has none. If the new image fails to come up, freshdock restores the previous container automatically and notifies you. No more waking up to a dead service.",[62,109,110,113,114,117,118,121],{},[26,111,112],{},"Opt-in by design."," Watchtower updates everything unless you exclude it. freshdock ignores every container until you set ",[31,115,116],{},"freshdock.enable=true",", and an enabled container with no mode defaults to ",[31,119,120],{},"watch"," (detect and notify, never restart).",[62,123,124,127],{},[26,125,126],{},"One small binary."," A single static Rust binary, ≤ 10 MB, instead of a runtime managing your other containers.",[11,129,130,131,134,135,139],{},"It's not a drop-in for ",[44,132,133],{},"every"," Watchtower setup. There's no dependency ordering, no \"update without pulling\", and Kubernetes and Swarm are deliberately out of scope. The ",[15,136,138],{"href":137},"\u002Fwatchtower-alternative","full comparison"," is honest about where each tool wins.",[36,141,143],{"id":142},"the-five-minute-version","The five-minute version",[11,145,146],{},"If you want to try it without risk, install it and run the read-only check first, since it never touches a container:",[148,149,154],"pre",{"className":150,"code":151,"language":152,"meta":153,"style":153},"language-bash shiki shiki-themes github-dark-high-contrast","cargo install freshdock\nfreshdock check\n","bash","",[31,155,156,172],{"__ignoreMap":153},[157,158,161,165,169],"span",{"class":159,"line":160},"line",1,[157,162,164],{"class":163},"s_sBn","cargo",[157,166,168],{"class":167},"sTRMh"," install",[157,170,171],{"class":167}," freshdock\n",[157,173,175,177],{"class":159,"line":174},2,[157,176,97],{"class":163},[157,178,179],{"class":167}," check\n",[11,181,182,183,186],{},"That prints a table of which containers have updates available. When you trust it, graduate one container to ",[31,184,185],{},"freshdock.mode=nightly"," and let the daemon take over.",[11,188,189,190,194,195,198,199,204],{},"Ready to switch? Start with the ",[15,191,193],{"href":192},"\u002Finstall","installation guide"," or read the ",[15,196,197],{"href":137},"step-by-step migration",". The full label-and-flag translation lives in the ",[15,200,203],{"href":201,"rel":202},"https:\u002F\u002Fturbootzz.github.io\u002Ffreshdock\u002Fmigrating-from-watchtower.html",[19],"migration guide on the docs site",".",[206,207,208],"style",{},"html pre.shiki code .s_sBn, html code.shiki .s_sBn{--shiki-default:#FFB757}html pre.shiki code .sTRMh, html code.shiki .sTRMh{--shiki-default:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":153,"searchDepth":210,"depth":210,"links":211},3,[212,213,214,215],{"id":38,"depth":174,"text":39},{"id":53,"depth":174,"text":54},{"id":90,"depth":174,"text":91},{"id":142,"depth":174,"text":143},"2026-06-23","Watchtower was archived in December 2025 and no longer works with Docker Engine 29+. Here are your real options, and how to move to a maintained replacement.",false,"md",{},true,null,"\u002Fblog\u002Fwatchtower-archived-what-to-do",{"title":6,"description":217},"blog\u002Fwatchtower-archived-what-to-do",[227,228],"watchtower","migration","vCJQefGYLgXpFkt2uyjTO2T1TRAg9RDg-FY-Z3401S8",{"id":231,"title":232,"body":233,"date":682,"description":683,"draft":218,"extension":219,"meta":684,"navigation":221,"ogImage":222,"path":685,"seo":686,"stem":687,"tags":688,"__hash__":689},"blog\u002Fblog\u002Fmigrating-from-watchtower-in-5-minutes.md","Migrating from Watchtower to freshdock in 5 minutes",{"type":8,"value":234,"toc":675},[235,244,248,251,281,287,291,302,374,389,393,396,504,507,612,625,629,632,643,650,654,665,672],[11,236,237,240,241,243],{},[15,238,239],{"href":223},"Watchtower is archived"," and breaks on Docker Engine 29+. Moving to ",[15,242,97],{"href":96}," is mostly a relabel plus a service swap. Here's the whole thing, start to finish.",[36,245,247],{"id":246},"_1-install-freshdock","1. Install freshdock",[11,249,250],{},"Pick whichever fits. The result is the same single binary:",[148,252,254],{"className":150,"code":253,"language":152,"meta":153,"style":153},"cargo install freshdock\n# or pull the multi-arch image\ndocker pull ghcr.io\u002Fturbootzz\u002Ffreshdock:latest\n",[31,255,256,264,270],{"__ignoreMap":153},[157,257,258,260,262],{"class":159,"line":160},[157,259,164],{"class":163},[157,261,168],{"class":167},[157,263,171],{"class":167},[157,265,266],{"class":159,"line":174},[157,267,269],{"class":268},"sQrFR","# or pull the multi-arch image\n",[157,271,272,275,278],{"class":159,"line":210},[157,273,274],{"class":163},"docker",[157,276,277],{"class":167}," pull",[157,279,280],{"class":167}," ghcr.io\u002Fturbootzz\u002Ffreshdock:latest\n",[11,282,283,284,204],{},"Full options on the ",[15,285,286],{"href":192},"install page",[36,288,290],{"id":289},"_2-translate-your-labels","2. Translate your labels",[11,292,293,294,297,298,301],{},"The concepts map closely; the spelling changes. The big one to internalise: ",[26,295,296],{},"freshdock is opt-in",", so you rarely need to ",[44,299,300],{},"disable"," anything. Unlabelled containers are simply ignored.",[303,304,305,316],"table",{},[306,307,308],"thead",{},[309,310,311,314],"tr",{},[312,313,20],"th",{},[312,315,97],{},[317,318,319,331,343,361],"tbody",{},[309,320,321,327],{},[322,323,324],"td",{},[31,325,326],{},"com.centurylinklabs.watchtower.enable=true",[322,328,329],{},[31,330,116],{},[309,332,333,338],{},[322,334,335],{},[31,336,337],{},"watchtower.monitor-only=true",[322,339,340],{},[31,341,342],{},"freshdock.mode=watch",[309,344,345,351],{},[322,346,347,350],{},[31,348,349],{},"WATCHTOWER_SCHEDULE"," (one global cron)",[322,352,353,354,357,358],{},"per-container ",[31,355,356],{},"freshdock.mode"," + ",[31,359,360],{},"freshdock.schedule",[309,362,363,369],{},[322,364,365,368],{},[31,366,367],{},"watchtower.enable=false"," (with global watch)",[322,370,371],{},[44,372,373],{},"just omit the labels",[11,375,376,377,380,381,384,385,388],{},"Two Watchtower features have no freshdock equivalent today: ",[31,378,379],{},"no-pull"," (freshdock always pulls before recreate) and ",[31,382,383],{},"depends-on"," dependency ordering. If you rely on those, check the ",[15,386,387],{"href":137},"comparison page"," before switching.",[36,390,392],{"id":391},"_3-swap-the-service-in-docker-compose","3. Swap the service in docker-compose",[11,394,395],{},"Replace the Watchtower service and relabel your apps. Before:",[148,397,401],{"className":398,"code":399,"language":400,"meta":153,"style":153},"language-yaml shiki shiki-themes github-dark-high-contrast","services:\n  app:\n    image: ghcr.io\u002Fexample\u002Fapp:latest\n    labels:\n      - \"com.centurylinklabs.watchtower.enable=true\"\n\n  watchtower:\n    image: containrrr\u002Fwatchtower\n    volumes:\n      - \u002Fvar\u002Frun\u002Fdocker.sock:\u002Fvar\u002Frun\u002Fdocker.sock\n    environment:\n      - WATCHTOWER_SCHEDULE=0 0 4 * * *\n","yaml",[31,402,403,413,420,431,439,448,454,462,472,480,488,496],{"__ignoreMap":153},[157,404,405,409],{"class":159,"line":160},[157,406,408],{"class":407},"sKpQp","services",[157,410,412],{"class":411},"sMAXC",":\n",[157,414,415,418],{"class":159,"line":174},[157,416,417],{"class":407},"  app",[157,419,412],{"class":411},[157,421,422,425,428],{"class":159,"line":210},[157,423,424],{"class":407},"    image",[157,426,427],{"class":411},": ",[157,429,430],{"class":167},"ghcr.io\u002Fexample\u002Fapp:latest\n",[157,432,434,437],{"class":159,"line":433},4,[157,435,436],{"class":407},"    labels",[157,438,412],{"class":411},[157,440,442,445],{"class":159,"line":441},5,[157,443,444],{"class":411},"      - ",[157,446,447],{"class":167},"\"com.centurylinklabs.watchtower.enable=true\"\n",[157,449,451],{"class":159,"line":450},6,[157,452,453],{"emptyLinePlaceholder":221},"\n",[157,455,457,460],{"class":159,"line":456},7,[157,458,459],{"class":407},"  watchtower",[157,461,412],{"class":411},[157,463,465,467,469],{"class":159,"line":464},8,[157,466,424],{"class":407},[157,468,427],{"class":411},[157,470,471],{"class":167},"containrrr\u002Fwatchtower\n",[157,473,475,478],{"class":159,"line":474},9,[157,476,477],{"class":407},"    volumes",[157,479,412],{"class":411},[157,481,483,485],{"class":159,"line":482},10,[157,484,444],{"class":411},[157,486,487],{"class":167},"\u002Fvar\u002Frun\u002Fdocker.sock:\u002Fvar\u002Frun\u002Fdocker.sock\n",[157,489,491,494],{"class":159,"line":490},11,[157,492,493],{"class":407},"    environment",[157,495,412],{"class":411},[157,497,499,501],{"class":159,"line":498},12,[157,500,444],{"class":411},[157,502,503],{"class":167},"WATCHTOWER_SCHEDULE=0 0 4 * * *\n",[11,505,506],{},"After:",[148,508,510],{"className":398,"code":509,"language":400,"meta":153,"style":153},"services:\n  app:\n    image: ghcr.io\u002Fexample\u002Fapp:latest\n    labels:\n      - \"freshdock.enable=true\"\n      - \"freshdock.mode=nightly\"   # 04:00 daily\n\n  freshdock:\n    image: ghcr.io\u002Fturbootzz\u002Ffreshdock:latest\n    command: [\"run\"]\n    volumes:\n      - \u002Fvar\u002Frun\u002Fdocker.sock:\u002Fvar\u002Frun\u002Fdocker.sock\n    restart: unless-stopped\n",[31,511,512,518,524,532,538,545,555,559,566,575,589,595,601],{"__ignoreMap":153},[157,513,514,516],{"class":159,"line":160},[157,515,408],{"class":407},[157,517,412],{"class":411},[157,519,520,522],{"class":159,"line":174},[157,521,417],{"class":407},[157,523,412],{"class":411},[157,525,526,528,530],{"class":159,"line":210},[157,527,424],{"class":407},[157,529,427],{"class":411},[157,531,430],{"class":167},[157,533,534,536],{"class":159,"line":433},[157,535,436],{"class":407},[157,537,412],{"class":411},[157,539,540,542],{"class":159,"line":441},[157,541,444],{"class":411},[157,543,544],{"class":167},"\"freshdock.enable=true\"\n",[157,546,547,549,552],{"class":159,"line":450},[157,548,444],{"class":411},[157,550,551],{"class":167},"\"freshdock.mode=nightly\"",[157,553,554],{"class":268},"   # 04:00 daily\n",[157,556,557],{"class":159,"line":456},[157,558,453],{"emptyLinePlaceholder":221},[157,560,561,564],{"class":159,"line":464},[157,562,563],{"class":407},"  freshdock",[157,565,412],{"class":411},[157,567,568,570,572],{"class":159,"line":474},[157,569,424],{"class":407},[157,571,427],{"class":411},[157,573,574],{"class":167},"ghcr.io\u002Fturbootzz\u002Ffreshdock:latest\n",[157,576,577,580,583,586],{"class":159,"line":482},[157,578,579],{"class":407},"    command",[157,581,582],{"class":411},": [",[157,584,585],{"class":167},"\"run\"",[157,587,588],{"class":411},"]\n",[157,590,591,593],{"class":159,"line":490},[157,592,477],{"class":407},[157,594,412],{"class":411},[157,596,597,599],{"class":159,"line":498},[157,598,444],{"class":411},[157,600,487],{"class":167},[157,602,604,607,609],{"class":159,"line":603},13,[157,605,606],{"class":407},"    restart",[157,608,427],{"class":411},[157,610,611],{"class":167},"unless-stopped\n",[11,613,614,615,618,619,621,622,204],{},"A read-only socket (",[31,616,617],{},":ro",") is enough while everything is on ",[31,620,120],{},"; give freshdock a writable socket once a container is on an updating mode like ",[31,623,624],{},"nightly",[36,626,628],{"id":627},"_4-verify-read-only-then-commit","4. Verify read-only, then commit",[11,630,631],{},"Before you let it change anything, run the read-only check. It lists your opted-in containers and what has updates, and never pulls, stops, or recreates:",[148,633,635],{"className":150,"code":634,"language":152,"meta":153,"style":153},"freshdock check\n",[31,636,637],{"__ignoreMap":153},[157,638,639,641],{"class":159,"line":160},[157,640,97],{"class":163},[157,642,179],{"class":167},[11,644,645,646,649],{},"Happy with the table? You're done. The daemon (",[31,647,648],{},"freshdock run",") will now health-gate every update and roll back any that fail to come up.",[36,651,653],{"id":652},"notifications-and-registries","Notifications and registries",[11,655,656,657,660,661,664],{},"If you used Watchtower's shoutrrr notifications, freshdock has webhook, Discord, Telegram, and SMTP backends, declared in a small ",[31,658,659],{},"freshdock.toml",", with secrets supplied via environment variables. Private registry credentials (Docker Hub, GHCR, Quay, lscr) come from ",[31,662,663],{},"FRESHDOCK_REGISTRY_*"," env vars, no file required.",[11,666,667,668,671],{},"The complete label, flag, env, notification, and registry translation table (more than fits here) is the ",[15,669,203],{"href":201,"rel":670},[19],". That's the authoritative reference; this post is just the five-minute path.",[206,673,674],{},"html pre.shiki code .s_sBn, html code.shiki .s_sBn{--shiki-default:#FFB757}html pre.shiki code .sTRMh, html code.shiki .sTRMh{--shiki-default:#ADDCFF}html pre.shiki code .sQrFR, html code.shiki .sQrFR{--shiki-default:#BDC4CC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sKpQp, html code.shiki .sKpQp{--shiki-default:#72F088}html pre.shiki code .sMAXC, html code.shiki .sMAXC{--shiki-default:#F0F3F6}",{"title":153,"searchDepth":210,"depth":210,"links":676},[677,678,679,680,681],{"id":246,"depth":174,"text":247},{"id":289,"depth":174,"text":290},{"id":391,"depth":174,"text":392},{"id":627,"depth":174,"text":628},{"id":652,"depth":174,"text":653},"2026-06-22","A practical, copy-paste migration from archived Watchtower to freshdock: translate labels, swap the service in docker-compose, and verify read-only first.",{},"\u002Fblog\u002Fmigrating-from-watchtower-in-5-minutes",{"title":232,"description":683},"blog\u002Fmigrating-from-watchtower-in-5-minutes",[228,227],"iDLSrpKV-rY5v5wAHFP7wq1zVKlq9SQR_94XbiPvmS8",{"id":691,"title":692,"body":693,"date":912,"description":913,"draft":218,"extension":219,"meta":914,"navigation":221,"ogImage":222,"path":915,"seo":916,"stem":917,"tags":918,"__hash__":921},"blog\u002Fblog\u002Fnotify-only-vs-auto-update.md","Notify-only vs auto-update: choosing a safe Docker update strategy",{"type":8,"value":694,"toc":905},[695,702,706,712,718,725,729,732,752,756,762,821,835,839,846,855,859,878,902],[11,696,697,698,701],{},"The loudest debate in container maintenance is framed as a binary: auto-update everything, or update nothing and patch by hand. Both extremes are wrong for most homelabs. The right answer is ",[44,699,700],{},"per container",", and freshdock is built around that.",[36,703,705],{"id":704},"the-two-failure-modes","The two failure modes",[11,707,708,711],{},[26,709,710],{},"Auto-update everything"," is how the \"Watchtower broke my server overnight\" stories happen. A bad upstream image ships, your tool pulls it at 4 a.m., the container won't start, and you find out when something you depend on is down.",[11,713,714,717],{},[26,715,716],{},"Update nothing"," feels safe but isn't. Stale images accumulate known vulnerabilities, and \"I'll get to it\" becomes months. The friction of manual updates is exactly why tools like this exist.",[11,719,720,721,724],{},"The useful question isn't \"auto or manual?\" It's \"what's the cost if ",[44,722,723],{},"this specific container"," updates badly, and how much do I trust its upstream?\"",[36,726,728],{"id":727},"a-simple-framework","A simple framework",[11,730,731],{},"Sort each container into one of three buckets:",[100,733,734,740,746],{},[62,735,736,739],{},[26,737,738],{},"Stateless and well-behaved"," (reverse proxies, dashboards, exporters). Low blast radius, easy to roll back. Good candidates for automatic updates.",[62,741,742,745],{},[26,743,744],{},"Stateful or critical"," (databases, auth, anything with a migration step). High cost if an update goes wrong. Keep these on notify-only and update them deliberately, when you can watch.",[62,747,748,751],{},[26,749,750],{},"Pinned on purpose"," (you need a specific version). Pin the digest and let the tool report it as pinned, no checks.",[36,753,755],{"id":754},"how-freshdock-expresses-this","How freshdock expresses this",[11,757,758,759,761],{},"Each opted-in container picks a ",[31,760,356],{},":",[148,763,765],{"className":398,"code":764,"language":400,"meta":153,"style":153},"# auto-update nightly, with the safety net\nlabels:\n  - \"freshdock.enable=true\"\n  - \"freshdock.mode=nightly\"\n\n# detect updates, but only tell me, never restart\nlabels:\n  - \"freshdock.enable=true\"\n  - \"freshdock.mode=watch\"\n",[31,766,767,772,779,786,793,797,802,808,814],{"__ignoreMap":153},[157,768,769],{"class":159,"line":160},[157,770,771],{"class":268},"# auto-update nightly, with the safety net\n",[157,773,774,777],{"class":159,"line":174},[157,775,776],{"class":407},"labels",[157,778,412],{"class":411},[157,780,781,784],{"class":159,"line":210},[157,782,783],{"class":411},"  - ",[157,785,544],{"class":167},[157,787,788,790],{"class":159,"line":433},[157,789,783],{"class":411},[157,791,792],{"class":167},"\"freshdock.mode=nightly\"\n",[157,794,795],{"class":159,"line":441},[157,796,453],{"emptyLinePlaceholder":221},[157,798,799],{"class":159,"line":450},[157,800,801],{"class":268},"# detect updates, but only tell me, never restart\n",[157,803,804,806],{"class":159,"line":456},[157,805,776],{"class":407},[157,807,412],{"class":411},[157,809,810,812],{"class":159,"line":464},[157,811,783],{"class":411},[157,813,544],{"class":167},[157,815,816,818],{"class":159,"line":474},[157,817,783],{"class":411},[157,819,820],{"class":167},"\"freshdock.mode=watch\"\n",[11,822,823,825,826,831,832,834],{},[31,824,120],{}," is the default, and it's a legitimate permanent choice. It's exactly what ",[15,827,830],{"href":828,"rel":829},"https:\u002F\u002Fgithub.com\u002Fcrazy-max\u002Fdiun",[19],"Diun"," does as its entire purpose: tell you an update exists and let you decide. freshdock just lets you mix ",[31,833,120],{}," and auto-update modes on the same daemon, container by container.",[36,836,838],{"id":837},"auto-update-is-safer-here-than-youd-expect","Auto-update is safer here than you'd expect",[11,840,841,842,845],{},"The usual argument against auto-update assumes a bad update leaves you broken. freshdock's health gate changes that calculus: an updated container has to pass its healthcheck (or a grace period) before the update is kept. If it doesn't, the previous container is restored automatically and you get a ",[31,843,844],{},"failed"," notification. The downside of auto-updating a stateless service shrinks a lot when \"it broke\" becomes \"it reverted and told me.\"",[11,847,848,849,851,852,854],{},"That's why a reasonable default for many homelabs is: stateless services on ",[31,850,624],{},", stateful services on ",[31,853,120],{},", and a healthcheck declared wherever it's cheap to add one, because the gate is only as good as the signal you give it.",[36,856,858],{"id":857},"start-conservative","Start conservative",[11,860,861,862,864,865,870,871,874,875,877],{},"You don't have to decide all of this up front. Install freshdock, label everything ",[31,863,120],{},", and run ",[15,866,867],{"href":192},[31,868,869],{},"freshdock check"," for a week. Watch what ",[44,872,873],{},"would"," have updated. Then promote the containers you trust to ",[31,876,624],{}," one at a time.",[11,879,880,881,885,886,891,892,895,896,899,900,204],{},"More on the modes and the health gate is on the ",[15,882,884],{"href":883},"\u002Ffeatures","features page"," and in the ",[15,887,890],{"href":888,"rel":889},"https:\u002F\u002Fturbootzz.github.io\u002Ffreshdock\u002Fscheduling.html",[19],"scheduling docs",". If you're coming from Watchtower's ",[31,893,894],{},"monitor-only",", the ",[15,897,898],{"href":137},"comparison"," maps it straight onto ",[31,901,120],{},[206,903,904],{},"html pre.shiki code .sQrFR, html code.shiki .sQrFR{--shiki-default:#BDC4CC}html pre.shiki code .sKpQp, html code.shiki .sKpQp{--shiki-default:#72F088}html pre.shiki code .sMAXC, html code.shiki .sMAXC{--shiki-default:#F0F3F6}html pre.shiki code .sTRMh, html code.shiki .sTRMh{--shiki-default:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":153,"searchDepth":210,"depth":210,"links":906},[907,908,909,910,911],{"id":704,"depth":174,"text":705},{"id":727,"depth":174,"text":728},{"id":754,"depth":174,"text":755},{"id":837,"depth":174,"text":838},{"id":857,"depth":174,"text":858},"2026-06-21","Should you auto-update Docker containers or get notified? A practical framework for choosing per-container update modes, and when notify-only is the right call.",{},"\u002Fblog\u002Fnotify-only-vs-auto-update",{"title":692,"description":913},"blog\u002Fnotify-only-vs-auto-update",[919,920],"strategy","homelab","I3YjomXepVY1T7MTEe5P5ERtS8JecPszmjfxrXfqonY",{"id":923,"title":924,"body":925,"date":1156,"description":1157,"draft":218,"extension":219,"meta":1158,"navigation":221,"ogImage":222,"path":1159,"seo":1160,"stem":1161,"tags":1162,"__hash__":1164},"blog\u002Fblog\u002Fhow-freshdock-decides-when-to-update.md","How freshdock decides when to update a container",{"type":8,"value":926,"toc":1149},[927,934,938,944,965,969,982,1021,1036,1040,1043,1054,1064,1068,1075,1078,1098,1104,1114,1118,1121,1146],[11,928,929,930,933],{},"freshdock's job sounds simple (\"update my containers\"), but the interesting part is everything it does ",[44,931,932],{},"not"," do automatically. Here's the full decision path, from \"should I even look at this container?\" to \"keep the update or roll it back?\"",[36,935,937],{"id":936},"step-1-is-this-container-opted-in","Step 1: is this container opted in?",[11,939,940,941,943],{},"freshdock is opt-in. A container with no ",[31,942,116],{}," label is invisible to it: no checks, no notifications, nothing. This is the opposite of Watchtower's update-everything default, and it's deliberate: you decide what freshdock manages, one label at a time.",[148,945,947],{"className":398,"code":946,"language":400,"meta":153,"style":153},"labels:\n  - \"freshdock.enable=true\"   # now freshdock can see it\n",[31,948,949,955],{"__ignoreMap":153},[157,950,951,953],{"class":159,"line":160},[157,952,776],{"class":407},[157,954,412],{"class":411},[157,956,957,959,962],{"class":159,"line":174},[157,958,783],{"class":411},[157,960,961],{"class":167},"\"freshdock.enable=true\"",[157,963,964],{"class":268},"   # now freshdock can see it\n",[36,966,968],{"id":967},"step-2-what-mode-is-it-in","Step 2: what mode is it in?",[11,970,971,972,974,975,978,979,761],{},"An enabled container picks a mode with ",[31,973,356],{},". The mode decides ",[44,976,977],{},"whether"," freshdock acts and ",[44,980,981],{},"when",[100,983,984,993,1003,1015],{},[62,985,986,988,989,992],{},[31,987,120],{},": detect updates and notify only. Never pulls, never restarts. ",[26,990,991],{},"This is the default"," for an enabled container with no explicit mode.",[62,994,995,998,999,1002],{},[31,996,997],{},"live",": pull and recreate on every new digest, checked every ",[31,1000,1001],{},"--interval"," (default 300s).",[62,1004,1005,1007,1008,1007,1011,1014],{},[31,1006,624],{}," \u002F ",[31,1009,1010],{},"weekly",[31,1012,1013],{},"monthly",": recreate if newer, on a cron schedule (default 04:00).",[62,1016,1017,1020],{},[31,1018,1019],{},"off",": ignored entirely.",[11,1022,1023,1024,1026,1027,1029,1030,1032,1033,1035],{},"A single daemon mixes these freely. Your reverse proxy can be on ",[31,1025,997],{},", your database on ",[31,1028,120],{},", your media server on ",[31,1031,1010],{},". Calendar modes take an optional ",[31,1034,360],{}," cron override.",[36,1037,1039],{"id":1038},"step-3-is-there-actually-a-newer-image","Step 3: is there actually a newer image?",[11,1041,1042],{},"When a container is due, freshdock resolves the latest digest for its image tag against the registry, using a rate-friendly HEAD request, not a full pull, and deduplicated so ten containers on the same image cost one lookup. It compares that digest to what the container is currently running.",[11,1044,1045,1046,1049,1050,1053],{},"If the digest is unchanged, freshdock stops here. If the image is pinned to a digest (",[31,1047,1048],{},"repo@sha256:…","), there's no moving tag to follow, so it's reported as ",[31,1051,1052],{},"pinned (no check)"," and never updated. You can see all of this without changing anything by running:",[148,1055,1056],{"className":150,"code":634,"language":152,"meta":153,"style":153},[31,1057,1058],{"__ignoreMap":153},[157,1059,1060,1062],{"class":159,"line":160},[157,1061,97],{"class":163},[157,1063,179],{"class":167},[36,1065,1067],{"id":1066},"step-4-the-health-gate","Step 4: the health gate",[11,1069,1070,1071,1074],{},"This is the part that makes unattended updates safe. For an updating mode with a newer digest, freshdock runs the recreate cycle: inspect the running container, pull the new image, stop the old one, rename it to an archive, create the new container from the ",[44,1072,1073],{},"exact same config",", and start it.",[11,1076,1077],{},"Then it waits. The container reaches one of three verdicts:",[100,1079,1080,1086,1092],{},[62,1081,1082,1085],{},[26,1083,1084],{},"Healthy",": a declared healthcheck reported healthy, or (no healthcheck) the container stayed up for the grace period. The archive is removed; the update stands.",[62,1087,1088,1091],{},[26,1089,1090],{},"Timeout",": a healthcheck was declared but never went healthy in time. Roll back.",[62,1093,1094,1097],{},[26,1095,1096],{},"Crashed",": the container exited before becoming healthy. Roll back.",[11,1099,1100,1101,1103],{},"On a rollback, freshdock removes the failed container, renames the archive back to the original name, and restarts it. You're left running exactly what you had, plus a ",[31,1102,844],{}," notification explaining why.",[1105,1106,1107],"blockquote",{},[11,1108,1109,1110,1113],{},"The health timeout (120s), the grace period for containers without a healthcheck (10s), and the poll interval (1s) are currently hardcoded. Declaring a ",[31,1111,1112],{},"HEALTHCHECK"," on your image gives the gate a much stronger signal than \"did it stay up?\".",[36,1115,1117],{"id":1116},"why-this-matters","Why this matters",[11,1119,1120],{},"The whole point is that freshdock will never knowingly leave you with a broken service. An update either passes its own health check and stays, or it's reverted automatically. That's the difference between \"set and forget\" being a feature and being a liability.",[11,1122,1123,1124,1129,1130,1134,1135,1138,1139,1141,1142,1145],{},"The exact verdict logic and timings are documented in ",[15,1125,1128],{"href":1126,"rel":1127},"https:\u002F\u002Fturbootzz.github.io\u002Ffreshdock\u002Fhealth-and-rollback.html",[19],"health & rollback","; the scheduling model is in ",[15,1131,1133],{"href":888,"rel":1132},[19],"scheduling",". When you're ready to try it, the ",[15,1136,1137],{"href":192},"install guide"," gets you to a read-only ",[31,1140,869],{}," in a minute, and the ",[15,1143,1144],{"href":137},"Watchtower comparison"," shows how this differs from the tool you're probably replacing.",[206,1147,1148],{},"html pre.shiki code .sKpQp, html code.shiki .sKpQp{--shiki-default:#72F088}html pre.shiki code .sMAXC, html code.shiki .sMAXC{--shiki-default:#F0F3F6}html pre.shiki code .sTRMh, html code.shiki .sTRMh{--shiki-default:#ADDCFF}html pre.shiki code .sQrFR, html code.shiki .sQrFR{--shiki-default:#BDC4CC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s_sBn, html code.shiki .s_sBn{--shiki-default:#FFB757}",{"title":153,"searchDepth":210,"depth":210,"links":1150},[1151,1152,1153,1154,1155],{"id":936,"depth":174,"text":937},{"id":967,"depth":174,"text":968},{"id":1038,"depth":174,"text":1039},{"id":1066,"depth":174,"text":1067},{"id":1116,"depth":174,"text":1117},"2026-06-19","A walk through freshdock's update logic: opt-in labels, per-container modes, digest checks, and the health gate that decides if an update sticks or rolls back.",{},"\u002Fblog\u002Fhow-freshdock-decides-when-to-update",{"title":924,"description":1157},"blog\u002Fhow-freshdock-decides-when-to-update",[1163,1133],"how-it-works","HOSYfxDRCF5upDGzNvwy2Z9ba-yb6C2Nry097LGJwps",{"id":1166,"title":1167,"body":1168,"date":1251,"description":1252,"draft":218,"extension":219,"meta":1253,"navigation":221,"ogImage":222,"path":1254,"seo":1255,"stem":1256,"tags":1257,"__hash__":1260},"blog\u002Fblog\u002Fwhy-rust-docker-updater.md","Why we built a Docker updater in Rust",{"type":8,"value":1169,"toc":1245},[1170,1173,1177,1184,1187,1191,1200,1207,1211,1214,1221,1224,1228,1231],[11,1171,1172],{},"Almost every Docker auto-updater in the wild is written in Go: Watchtower, Diun, What's Up Docker, the lot. So why write another one in Rust? Not for novelty. Three concrete reasons.",[36,1174,1176],{"id":1175},"a-small-binary-that-doesnt-manage-your-homelab-with-a-runtime","A small binary that doesn't manage your homelab with a runtime",[11,1178,1179,1180,1183],{},"The thing that updates your containers shouldn't be the heaviest container on the box. freshdock compiles to a ",[26,1181,1182],{},"single static-musl binary, ≤ 10 MB",", with no runtime dependencies: no JVM, no language runtime, no 100 MB image. The multi-arch container image (amd64, arm64, armv7) is a thin wrapper around that binary.",[11,1185,1186],{},"This matters most on the hardware homelabbers actually run: a Raspberry Pi, an old NUC, a NAS. A tool whose footprint is rounding error is a tool you forget is even there.",[36,1188,1190],{"id":1189},"modern-docker-via-bollard","Modern Docker, via bollard",[11,1192,1193,1194,1199],{},"Watchtower's fatal flaw wasn't the language. It was an embedded Docker SDK pinned to API 1.25 that can't talk to Docker Engine 29+. freshdock uses ",[15,1195,1198],{"href":1196,"rel":1197},"https:\u002F\u002Fgithub.com\u002Ffussybeaver\u002Fbollard",[19],"bollard",", a mature Rust Docker client that auto-negotiates the API version. It's tested against Docker 24.x through 29+, and it speaks Podman's Docker-compatible socket without changes.",[11,1201,1202,1203,1206],{},"The language helped here in a quieter way: bollard's types make the hardest part of the whole project, faithfully recreating a container with the ",[44,1204,1205],{},"exact"," same config, something the compiler helps you get right.",[36,1208,1210],{"id":1209},"the-recreate-problem-wants-a-state-machine","The recreate problem wants a state machine",[11,1212,1213],{},"\"Restart the container with the same options\" is the single most error-prone thing an updater does. Get a network alias, a mount, a capability, or a restart policy wrong and you've silently broken a service.",[11,1215,1216,1217,1220],{},"freshdock captures the running container's full config, maps it onto a fresh container with the new image, and a dedicated round-trip test asserts the recreated config comes back byte-identical except for the image and container ID. That test is the project's quality gate. Rust's enums and exhaustive matching make the update ",[44,1218,1219],{},"lifecycle"," (inspect, pull, stop, rename, create, start, health-gate, then either succeed or roll back) a state machine where the \"what if this step fails?\" branch is impossible to forget, because the compiler won't let you.",[11,1222,1223],{},"That rollback path is the whole point. An update either proves healthy and stays, or it's reverted automatically. Async is handled with Tokio; the cron parser and scheduler are hand-rolled to keep the dependency surface small (chrono is pulled in only for DST-correct local-time math).",[36,1225,1227],{"id":1226},"being-honest-about-it","Being honest about it",[11,1229,1230],{},"Rust didn't make freshdock automatically better than the Go tools. Go is a perfectly good choice and those tools are mature. What Rust bought us, specifically, is a tiny static binary and a type system that makes the safety-critical recreate logic hard to get subtly wrong. For a tool whose failure mode is \"your service is down and you're asleep,\" that trade was worth making.",[11,1232,1233,1234,1236,1237,1239,1240,204],{},"If that resonates, the ",[15,1235,884],{"href":883}," walks through the health-gated lifecycle in detail, and the ",[15,1238,1137],{"href":192}," gets you running in a minute. The original design rationale (goals, non-goals, and the phased roadmap) lives in the ",[15,1241,1244],{"href":1242,"rel":1243},"https:\u002F\u002Fturbootzz.github.io\u002Ffreshdock\u002FPLAN.html",[19],"architecture doc",{"title":153,"searchDepth":210,"depth":210,"links":1246},[1247,1248,1249,1250],{"id":1175,"depth":174,"text":1176},{"id":1189,"depth":174,"text":1190},{"id":1209,"depth":174,"text":1210},{"id":1226,"depth":174,"text":1227},"2026-06-17","Most Docker update tools are Go; freshdock is Rust. The reasoning: a tiny static binary, modern Docker via bollard, and a safety-first update state machine.",{},"\u002Fblog\u002Fwhy-rust-docker-updater",{"title":1167,"description":1252},"blog\u002Fwhy-rust-docker-updater",[1258,1259],"rust","design","r0Bxi1r0OosOugubS1bM5HK836aeuW53uKz2FUpW13w",1782377496639]