<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Articles | Coders' Compass Publishing</title><link>https://coderscompass.org/articles/</link><description>A list of articles published by Coders' Compass.</description><language>en-gb</language><copyright>Coders' Compass Publishing</copyright><generator>Hugo -- gohugo.io</generator><lastBuildDate>Mon, 01 Jun 2026 12:00:00 +0530</lastBuildDate><atom:link href="https://coderscompass.org/articles/index.xml" rel="self" type="application/rss+xml"/><item><title>What is RSS, and How Do I Subscribe to Coders' Compass?</title><link>https://coderscompass.org/articles/what-is-rss-and-how-to-subscribe/</link><guid isPermaLink="true">https://coderscompass.org/articles/what-is-rss-and-how-to-subscribe/</guid><pubDate>Mon, 01 Jun 2026 12:00:00 +0530</pubDate><dc:creator>Subhomoy Haldar</dc:creator><description>A plain-English explanation of RSS, a short tour of feed reader options including FOSS picks, and step-by-step instructions for subscribing to our feeds.</description><content:encoded>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;You’ve probably seen the little RSS icons next to &lt;a href="https://coderscompass.org/articles/"&gt;Articles&lt;/a&gt; and &lt;a href="https://coderscompass.org/free-chapters/"&gt;Free chapters&lt;/a&gt; in the footer and on the section pages. If you’re new to RSS, those icons might not mean much to you. This post aims to fill in the missing pieces. We explain what RSS is, why we publish through it, and then briefly, how to read our posts in a feed reader of your choice.&lt;/p&gt;
&lt;h2 id="what-does-rss-mean"&gt;What Does RSS Mean?&lt;/h2&gt;
&lt;p&gt;RSS (short for &lt;em&gt;Really Simple Syndication&lt;/em&gt;) is a small, machine-readable file that a website publishes alongside its regular pages. Every time we publish a new article, our RSS file updates with that article’s title, a summary, and a link back to the full version. A &lt;em&gt;feed reader&lt;/em&gt; repeatedly checks our RSS file on your behalf and shows you what is new. More importantly, it is software that you control.&lt;/p&gt;
&lt;p&gt;This hails from an earlier era of the internet. The standard goes back to the late 1990s, and the protocol-level history is summarised well on the &lt;a href="https://en.wikipedia.org/wiki/RSS"&gt;Wikipedia page for RSS&lt;/a&gt;. A close cousin called &lt;a href="https://en.wikipedia.org/wiki/Atom_(web_standard)"&gt;Atom&lt;/a&gt; does the same job with a slightly different XML format. Most feed readers can handle both interchangeably, so the distinction rarely matters in practice.&lt;/p&gt;
&lt;h2 id="why-would-i-need-it"&gt;Why Would I Need It?&lt;/h2&gt;
&lt;p&gt;Unlike email newsletters and social media timelines, a feed reader provides three distinct advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No account or email address is needed.&lt;/strong&gt; You add our feed URL to your reader, and you’re done. We never learn that you subscribed, which is the entire point.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No algorithm reorders your reading.&lt;/strong&gt; Posts appear in the order they were published. A week of silence from us means no new updates in your feed reader. You won’t find any surprise filler, “people you may know” features, or engagement loops here.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Portability.&lt;/strong&gt; Your subscription list is yours. Most readers export to an &lt;a href="https://en.wikipedia.org/wiki/OPML"&gt;OPML file&lt;/a&gt;, and any other reader will import that file. You have the freedom to use any platform you choose.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-do-i-set-up-feed-readers"&gt;How Do I Set Up Feed Readers?&lt;/h2&gt;
&lt;p&gt;There is no single best reader. Your ideal choice hinges on your preference for self-hosting, your primary reading device (mobile or desktop), and your budget (&lt;em&gt;which is often $0&lt;/em&gt;). The shortlist below is FOSS-first. In the interest of full transparency, this post’s author operates &lt;a href="https://miniflux.app/"&gt;Miniflux&lt;/a&gt; as the back-end and &lt;a href="https://github.com/jocmp/capyreader"&gt;CapyReader&lt;/a&gt; on Android.&lt;/p&gt;
&lt;h3 id="durable-subscription-options"&gt;Durable Subscription Options&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://miniflux.app/"&gt;Miniflux&lt;/a&gt;&lt;/strong&gt; is a minimalist, fast, single-binary self-hosted reader written in Go. Licensed under &lt;a href="https://github.com/miniflux/v2/blob/main/LICENSE"&gt;Apache 2.0&lt;/a&gt;. Note that it requires PostgreSQL. You can self-host it with &lt;a href="https://miniflux.app/docs/docker.html#docker-compose"&gt;Docker Compose&lt;/a&gt;. If self-hosting feels like too much, the same developers run a hosted plan at &lt;a href="https://miniflux.app/"&gt;miniflux.app&lt;/a&gt; for roughly &lt;strong&gt;$15/year&lt;/strong&gt; (excellent value). The project also accepts &lt;a href="https://en.liberapay.com/Miniflux/"&gt;donations via Liberapay&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://freshrss.org/"&gt;FreshRSS&lt;/a&gt;&lt;/strong&gt; is also multi-user PHP self-hosted reader with &lt;a href="https://github.com/FreshRSS/FreshRSS/tree/edge/Docker#docker-compose"&gt;Docker Compose setup&lt;/a&gt; and broader database support. Released under the &lt;a href="https://github.com/FreshRSS/FreshRSS/blob/edge/LICENSE.txt"&gt;AGPL-3.0 licence&lt;/a&gt; and accepting donations via its &lt;a href="https://liberapay.com/FreshRSS/donate"&gt;Liberapay&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both speak the long-standing Google Reader and Fever APIs, so a wide range of mobile and desktop clients can sync against either of them (confirmed by the official docs: &lt;a href="https://miniflux.app/docs/"&gt;Miniflux&lt;/a&gt;, &lt;a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html"&gt;FreshRSS&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id="options-for-reading-on-your-devices"&gt;Options For Reading On Your Devices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://netnewswire.com/"&gt;NetNewsWire&lt;/a&gt;&lt;/strong&gt; is a native macOS and iOS app under the &lt;a href="https://github.com/Ranchero-Software/NetNewsWire/blob/main/LICENSE"&gt;MIT licence&lt;/a&gt;. Syncs via iCloud. There is no paid tier; the project runs on volunteer time. If you really want to help, they suggest &lt;a href="https://github.com/Ranchero-Software/NetNewsWire/blob/main/Technotes/HowToSupportNetNewsWire.markdown"&gt;some excellent ways&lt;/a&gt; to do so while &lt;em&gt;spending no money&lt;/em&gt; on them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/jocmp/capyreader"&gt;CapyReader&lt;/a&gt;&lt;/strong&gt; is an Android client (also on &lt;a href="https://f-droid.org/packages/com.capyreader.app/"&gt;F-Droid&lt;/a&gt;) that pairs cleanly with Miniflux, FreshRSS, and other Fever/Google Reader back-ends. The developer accepts &lt;a href="https://ko-fi.com/capyreader"&gt;donations on Ko-fi&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://gitlab.com/news-flash/news_flash_gtk"&gt;NewsFlash&lt;/a&gt;&lt;/strong&gt; is a GTK reader for Linux desktops, suitable for GNOME users who would like a native experience. Written mostly in Rust, it is a spiritual successor to &lt;a href="https://github.com/jangernert/FeedReader"&gt;FeedReader&lt;/a&gt;. It is licensed under &lt;a href="https://gitlab.com/news-flash/news_flash_gtk/-/blob/main/LICENSE"&gt;GPL v3&lt;/a&gt;. The ecommended way of helping the project is to &lt;a href="https://gitlab.com/news-flash/news_flash_gtk#looking-for-service-maintainers"&gt;volunteer your time&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="web-hosted-options"&gt;Web-Hosted Options&lt;/h3&gt;
&lt;p&gt;If you would rather not self-host or install an app, two well-known web readers cover that ground: &lt;a href="https://www.inoreader.com/"&gt;Inoreader&lt;/a&gt; and &lt;a href="https://feedly.com/"&gt;Feedly&lt;/a&gt;. Both are closed-source and use a freemium model. Inoreader’s Pro tier sits around &lt;a href="https://www.inoreader.com/pricing"&gt;€80/year&lt;/a&gt;, and Feedly now positions itself primarily as an &lt;a href="https://feedly.com/ai"&gt;AI-driven enterprise intelligence platform&lt;/a&gt;, with its free tier capped at 100 sources. You can still use them as free starter accounts. We mention them to ensure the list accurately reflects the non-FOSS landscape.&lt;/p&gt;
&lt;p&gt;Once you have chosen a reader, the rest of this post applies to all of them.&lt;/p&gt;
&lt;h2 id="subscribe-to-coders-compass"&gt;Subscribe To Coders&amp;rsquo; Compass&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Prerequisite:&lt;/strong&gt; a feed reader installed and open, with its &amp;ldquo;Add subscription&amp;rdquo; (or &amp;ldquo;New feed&amp;rdquo;) screen visible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1.&lt;/strong&gt; Copy whichever feed URL matches what you want to follow:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-md" data-lang="md"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ef9f76;font-weight:bold"&gt;# Articles:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span&gt;https://www.coderscompass.org/articles/index.xml
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-5"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ef9f76;font-weight:bold"&gt;# Free chapters:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-6"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-7"&gt;7&lt;/a&gt;&lt;/span&gt;&lt;span&gt;https://www.coderscompass.org/free-chapters/index.xml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Step 2.&lt;/strong&gt; Paste the URL into the &amp;ldquo;Add subscription&amp;rdquo; field in your reader.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3.&lt;/strong&gt; Confirm. Your reader should fetch the feed within a few seconds and show a list of recent posts. If it does not, double-check the URL has not picked up any leading or trailing whitespace.&lt;/p&gt;
&lt;p&gt;If pasting the explicit &lt;code&gt;.xml&lt;/code&gt; URL is fiddly, most readers also accept the homepage URL &lt;code&gt;https://www.coderscompass.org/&lt;/code&gt; and discover the feeds automatically through the &lt;code&gt;&amp;lt;link rel=&amp;quot;alternate&amp;quot;&amp;gt;&lt;/code&gt; tags we include in the HTML head. Either route works.&lt;/p&gt;
&lt;h2 id="troubleshooting"&gt;Troubleshooting&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The reader fetched the feed but shows nothing.&lt;/strong&gt; Most readers poll on an interval. Wait a few minutes and refresh, or trigger a manual refresh from the reader&amp;rsquo;s menu.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The reader refuses the URL.&lt;/strong&gt; Try the homepage URL &lt;code&gt;https://www.coderscompass.org/&lt;/code&gt; instead and let the reader auto-discover.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your reader complains about TLS.&lt;/strong&gt; We serve HTTPS with a current certificate, so no action is needed on your side. If the reader still complains, it is almost certainly out of date.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That should be everything you need to get our articles and chapters into your reading rhythm. If you run into any issues, ask for help in the comments below, or &lt;a href="https://coderscompass.org/contact/"&gt;get in touch&lt;/a&gt; with us.&lt;/p&gt;</content:encoded><category>self-hosting</category><category>privacy</category><category>beginners</category></item><item><title>Never Locked Out: Monitoring SSH Reachability with Uptime Kuma and Tailscale</title><link>https://coderscompass.org/articles/monitoring-ssh-reachability-with-uptime-kuma-and-tailscale/</link><guid isPermaLink="true">https://coderscompass.org/articles/monitoring-ssh-reachability-with-uptime-kuma-and-tailscale/</guid><pubDate>Mon, 18 May 2026 12:00:00 +0530</pubDate><dc:creator>Subhomoy Haldar</dc:creator><description>Use Uptime Kuma's TCP port monitor across your Tailscale tailnet to catch broken SSH before you actually need to log in.</description><content:encoded>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This is a small, focused application of &lt;a href="https://github.com/louislam/uptime-kuma"&gt;Uptime Kuma&lt;/a&gt;. Out of the many things you can monitor with it, a TCP port check is one of the simplest. I use it to watch port 22 on every machine in my &lt;a href="https://tailscale.com/"&gt;Tailscale&lt;/a&gt; tailnet, so I find out about a broken SSH path &lt;em&gt;before&lt;/em&gt; I&amp;rsquo;m trying to fix something else and discover I&amp;rsquo;m locked out.&lt;/p&gt;
&lt;p&gt;If you haven&amp;rsquo;t set up Uptime Kuma yet, we have a &lt;a href="https://coderscompass.org/articles/uptime-kuma-self-hosted-monitoring-tool/"&gt;dedicated article on installing it&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="why-monitor-ssh"&gt;Why Monitor SSH?&lt;/h2&gt;
&lt;p&gt;SSH is the entry point to remote machines. This door rarely fails loudly. &lt;code&gt;sshd&lt;/code&gt; can crash after an upgrade, a kernel update may need a reboot you forgot to do, a host firewall rule can quietly start dropping inbound 22, or a careless edit to &lt;code&gt;sshd_config&lt;/code&gt; might lock out your user with no one noticing. In every case, the box keeps running. You only realise the door is shut when you next try to walk through it, which is usually the moment you actually need it.&lt;/p&gt;
&lt;p&gt;A passive check every minute or two costs next to nothing and transforms “that machine won’t let me in” into a notification that arrives hours sooner.&lt;/p&gt;
&lt;p&gt;Note that this covers the necessity of monitoring. By itself, it will not do much unless you have a fallback mechanism for regaining access to your machine(s). We talk about it in &lt;a href="#fallback-paths-before-you-need-them"&gt;this section&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A working &lt;a href="https://coderscompass.org/articles/uptime-kuma-self-hosted-monitoring-tool/"&gt;Uptime Kuma&lt;/a&gt; instance with at least one notification channel configured.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailscale.com/"&gt;Tailscale&lt;/a&gt; installed on the machines you want to monitor, with &lt;a href="https://tailscale.com/docs/features/magicdns"&gt;MagicDNS&lt;/a&gt; enabled.&lt;/li&gt;
&lt;li&gt;The Uptime Kuma host on the same tailnet, so it can resolve MagicDNS names and reach the other machines over the WireGuard mesh.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sshd&lt;/code&gt; listening on port 22 on each target machine. If you&amp;rsquo;ve moved SSH to a different port, substitute that everywhere below.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="verification"&gt;Verification&lt;/h2&gt;
&lt;p&gt;Before adding monitors, confirm the Uptime Kuma host can actually reach each machine over Tailscale. Run these from the host that runs Uptime Kuma.&lt;/p&gt;
&lt;p&gt;Check that Tailscale is up:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;tailscale status
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should see a list of peers in your tailnet with their MagicDNS short names in the first column.&lt;/p&gt;
&lt;p&gt;Resolve a MagicDNS short name to a &lt;code&gt;100.x.y.z&lt;/code&gt; Tailscale address:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-1-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;tailscale ip gandalf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;gandalf&lt;/code&gt; with one of your machine names. Expected output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-2-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;100.64.0.12
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-2-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span&gt;fd7a:115c:a1e0::abcd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Confirm port 22 actually answers from this host:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-3-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;nc -zv gandalf &lt;span style="color:#ef9f76"&gt;22&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-4-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;Connection to gandalf (100.64.0.12) 22 port [tcp/ssh] succeeded!
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If any of those fail, fix the Tailscale side first. Uptime Kuma will see exactly what your shell sees.&lt;/p&gt;
&lt;h2 id="add-a-tcp-port-monitor"&gt;Add a TCP Port Monitor&lt;/h2&gt;
&lt;p&gt;In the Uptime Kuma dashboard, click &lt;strong&gt;Add New Monitor&lt;/strong&gt;. Set &lt;strong&gt;Monitor Type&lt;/strong&gt; to &lt;strong&gt;TCP Port&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/ssh-reachability/kuma-tcp-monitor-ssh.webp" alt="TCP Port monitor configured for SSH with a MagicDNS hostname and port 22" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;Fill in the form:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Friendly Name&lt;/strong&gt;: something like &lt;code&gt;SSH: gandalf&lt;/code&gt;. When you have a dozen machines, prefixing with &lt;code&gt;SSH:&lt;/code&gt; lets you filter the dashboard at a glance. For further organisation, you can create groups of monitors in Uptime Kuma as well.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hostname&lt;/strong&gt;: the MagicDNS short name (&lt;code&gt;gandalf&lt;/code&gt;) or the Fully Qualified Domain Name (FQDN): &lt;code&gt;gandalf.tailnet-name.ts.net&lt;/code&gt;. Tailscale will resolve either over the tailnet. I personally use the FQDN.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Port&lt;/strong&gt;: &lt;code&gt;22&lt;/code&gt; for SSH.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Heartbeat Interval&lt;/strong&gt;: &lt;code&gt;300&lt;/code&gt; seconds (five minutes) is a sensible default for SSH. It&amp;rsquo;s fast enough to find out before you need the box, slow enough to keep &lt;code&gt;sshd&lt;/code&gt; logs quiet. Drop to &lt;code&gt;60&lt;/code&gt; for snappier alerts on a fast-moving network.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retries&lt;/strong&gt;: &lt;code&gt;2&lt;/code&gt; is plenty.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Heartbeat Retry Interval&lt;/strong&gt;: &lt;code&gt;300&lt;/code&gt; seconds. Matches the heartbeat interval. Two unsuccessful retries before alerting will manage minor, temporary issues without unnecessary warnings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notifications&lt;/strong&gt;: attach whichever channel you&amp;rsquo;ve configured (email, ntfy, Discord, Telegram, and so on).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Save it, then repeat for every machine you care about. To save time, clone the first and change the hostname for the rest. A dashboard with one tile per machine takes minutes to set up and pays for itself the first time something quietly breaks.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/ssh-reachability/kuma-dashboard-ssh.webp" alt="Uptime Kuma dashboard with several SSH monitors showing green heartbeats" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;h2 id="going-further-catching-accidental-public-exposure"&gt;Going Further: Catching Accidental Public Exposure&lt;/h2&gt;
&lt;p&gt;Let us now consider the inverse problem. The setup above tells you when SSH stops working &lt;em&gt;over the tailnet&lt;/em&gt;. It doesn’t tell you when SSH works &lt;em&gt;from somewhere it shouldn’t&lt;/em&gt;. If a host firewall rule lapses, or &lt;code&gt;sshd&lt;/code&gt; ends up bound to a public interface after a careless config edit, your tailnet-only assumption breaks silently.&lt;/p&gt;
&lt;p&gt;A second Uptime Kuma instance running &lt;em&gt;outside&lt;/em&gt; your tailnet (say, on a small VPS or a friend&amp;rsquo;s home server) can catch this. Add a TCP Port monitor for the public hostname or IP of each machine on port 22, and turn on Uptime Kuma&amp;rsquo;s &lt;a href="https://github.com/louislam/uptime-kuma/blob/master/server/model/monitor.js"&gt;Upside Down Mode&lt;/a&gt;. With that toggled, &amp;ldquo;reachable&amp;rdquo; registers as DOWN. The notification fires when SSH is unexpectedly reachable from the public internet.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I haven&amp;rsquo;t deployed this myself. The boxes are firewalled at the cloud-provider level, and I&amp;rsquo;ve trusted that single layer. If you do set it up, the wiring is the same shape as a normal TCP monitor. There&amp;rsquo;s a toggle to flip the monitoring to alert if something&amp;rsquo;s up instead of down.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are other alternative approaches available. Upside Down mode on Uptime Kuma is pragmatic because we&amp;rsquo;re already self-hosting Uptime Kuma and we don&amp;rsquo;t want another tool to maintain.&lt;/p&gt;
&lt;h2 id="fallback-paths-before-you-need-them"&gt;Fallback Paths Before You Need Them&lt;/h2&gt;
&lt;p&gt;Monitoring alerts you to a failure. The actual fix needs to be administered through fallback paths. The belt and braces approach means one supports the other; they aren’t interchangeable.&lt;/p&gt;
&lt;p&gt;Confirm that the out-of-band console from your provider works on VPS-hosted machines prior to losing SSH access. Hetzner Cloud&amp;rsquo;s &lt;a href="https://docs.hetzner.com/cloud/servers/getting-started/rescue-system/"&gt;Rescue System&lt;/a&gt; or the &lt;a href="https://docs.hetzner.com/cloud/servers/getting-started/vnc-console"&gt;console&lt;/a&gt;, Akamai Linode&amp;rsquo;s &lt;a href="https://techdocs.akamai.com/cloud-computing/docs/access-your-system-console-using-lish"&gt;Lish&lt;/a&gt;, and DigitalOcean&amp;rsquo;s &lt;a href="https://docs.digitalocean.com/products/droplets/how-to/recovery/recovery-console/"&gt;Recovery Console&lt;/a&gt; all let you in over the provider&amp;rsquo;s own channel when the network path is broken. The equivalents on AWS, OCI, and Vultr work the same way. Boot one of your droplets into rescue mode now, log in once, and bookmark/save the page.&lt;/p&gt;
&lt;p&gt;For machines I can physically touch, the fallback is dumber and more reliable: a USB keyboard, a working HDMI cable, and a display I&amp;rsquo;ve tested. I do this check before installing Tailscale on any machine I bring online, so I know I can recover the box even when the network is broken.&lt;/p&gt;
&lt;p&gt;The full stack ends up being: Tailscale to reach the box, an Uptime Kuma monitor to tell me when that stops working, and an out-of-band path for the day both layers fail at once.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;A simple TCP port monitor on port 22 over Tailscale&amp;rsquo;s Magic DNS is a very small but very powerful application of Uptime Kuma. This automation has saved me from guessing &amp;ldquo;when did this particular machine go down?&amp;rdquo; You can apply this same technique for any TCP service that you want to keep an eye on inside your tailnet. It can be a Redis instance, a Postgres database, a Minecraft server, or anything that listens on a port.&lt;/p&gt;
&lt;p&gt;How do you keep yourself from getting locked out of your own machines? Share it in a comment below!&lt;/p&gt;</content:encoded><category>self-hosting</category><category>utilities</category><category>uptime-monitoring</category><category>tailscale</category><category>privacy</category></item><item><title>A Dead Man's Switch for Docker Compose Updates with Uptime Kuma</title><link>https://coderscompass.org/articles/automating-docker-compose-updates-with-uptime-kuma/</link><guid isPermaLink="true">https://coderscompass.org/articles/automating-docker-compose-updates-with-uptime-kuma/</guid><pubDate>Fri, 15 May 2026 10:00:00 +0530</pubDate><dc:creator>Subhomoy Haldar</dc:creator><description>A dead man's switch for self-hosted Docker Compose stacks: two shell scripts, a couple cron entries, and a push monitor on Uptime Kuma.</description><content:encoded>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;If you self-host a few Docker Compose stacks, keeping them updated means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSH-ing in&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cd&lt;/code&gt;-ing into each app directory&lt;/li&gt;
&lt;li&gt;running &lt;code&gt;docker compose pull &amp;amp;&amp;amp; docker compose up -d&lt;/code&gt; for each one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you have more than two or three stacks, this gets tedious. And if a pull fails at 03:00 on a Sunday, you probably won&amp;rsquo;t notice until something stops working days later.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/containrrr/watchtower"&gt;Watchtower&lt;/a&gt; used to handle this. It watched running containers and pulled new images automatically. The original project was &lt;a href="https://github.com/containrrr/watchtower/discussions/2135"&gt;archived by its maintainers on 17 December 2025&lt;/a&gt; with no further releases planned. There are good alternatives now: &lt;a href="https://github.com/nicholas-fedor/watchtower"&gt;nicholas-fedor/watchtower&lt;/a&gt; is a maintained fork, &lt;a href="https://github.com/getwud/wud"&gt;What&amp;rsquo;s Up Docker (WUD)&lt;/a&gt; does image-update notifications, and &lt;a href="https://github.com/crazy-max/diun"&gt;Diun&lt;/a&gt; handles registry-level change detection.&lt;/p&gt;
&lt;p&gt;We went with a different approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;two shell scripts&lt;/li&gt;
&lt;li&gt;a couple cron entries&lt;/li&gt;
&lt;li&gt;a push monitor on &lt;a href="https://github.com/louislam/uptime-kuma"&gt;Uptime Kuma&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The scripts are about thirty lines total. There&amp;rsquo;s no daemon to run, no configuration file to maintain. Cron triggers the update weekly, and Uptime Kuma tells us whether it worked.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/louislam/uptime-kuma"&gt;Uptime Kuma&lt;/a&gt; is a self-hosted monitoring tool. We have a &lt;a href="https://coderscompass.org/articles/uptime-kuma-self-hosted-monitoring-tool/"&gt;dedicated article on setting it up&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="working-principle-the-dead-mans-switch"&gt;Working Principle: The Dead Man&amp;rsquo;s Switch&lt;/h2&gt;
&lt;p&gt;The technique we&amp;rsquo;re using has a name. A &lt;a href="https://en.wikipedia.org/wiki/Dead_man%27s_switch"&gt;dead man&amp;rsquo;s switch&lt;/a&gt; is a control that defaults to &amp;ldquo;stop&amp;rdquo; and must be actively held open by a live signal. The term comes from late-1800s electric trams and trains, where the operator&amp;rsquo;s handle is spring-loaded so that if the operator slumps or releases the handle, the controller cuts power and engages the brakes. The pattern saw widespread adoption in US transit after the Malbone Street wreck of 1918.&lt;/p&gt;
&lt;p&gt;The same inverted logic drives this pipeline. Each successful run of &lt;code&gt;update.sh&lt;/code&gt; ends with a curl ping to the Uptime Kuma push monitor. The monitor&amp;rsquo;s 7d+1h window is the &amp;ldquo;held-open&amp;rdquo; interval. If cron never fires, the script aborts before reaching the notifier, the server is offline, or the network is dropping requests. As a result, no ping arrives. After the retries expire, the monitor flips to &lt;em&gt;down&lt;/em&gt; and Kuma fires whatever notification channel you&amp;rsquo;ve configured. The script died and the switch flips to alert us: something went wrong.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We sometimes send a ping within the held-open interval for failures detected by the script. We sent a signal through to Uptime Kuma even though the interval has not closed yet. That’s when we’re absolutely sure that something’s definitely gone wrong and we should get informed about it right away.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Several services exist purely for this pattern: &lt;a href="https://healthchecks.io/docs/"&gt;Healthchecks.io&lt;/a&gt; is the canonical open-source one, &lt;a href="https://deadmanssnitch.com/"&gt;Dead Man&amp;rsquo;s Snitch&lt;/a&gt; is the commercial counterpart that popularised the name, and Prometheus ships a &lt;a href="https://blog.ediri.io/how-to-set-up-a-dead-mans-switch-in-prometheus"&gt;Watchdog alert&lt;/a&gt; so you can use a dead man&amp;rsquo;s switch to monitor your own alerting pipeline. Uptime Kuma&amp;rsquo;s push-type monitor is the same mechanism — we use it here because we already self-host Kuma and don&amp;rsquo;t want another service to maintain.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-started/get-docker/"&gt;Docker&lt;/a&gt; version 20.10 or later with the Compose plugin.&lt;/li&gt;
&lt;li&gt;One or more Docker Compose stacks you already run, each in its own directory (e.g. &lt;code&gt;~/apps/it-tools/docker-compose.yaml&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A working &lt;a href="https://coderscompass.org/articles/uptime-kuma-self-hosted-monitoring-tool/"&gt;Uptime Kuma&lt;/a&gt; instance with the ability to create a Push-type monitor.&lt;/li&gt;
&lt;li&gt;SSH access to the server that runs your Docker stacks, and the ability to edit the crontab on it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="verification"&gt;Verification&lt;/h2&gt;
&lt;p&gt;Everything from this point on happens on the remote server, not your local machine. Set up your keys (recommended) and SSH in first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;ssh user@your-server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s check that the required tools are available next:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-1-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker --version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-2-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;Docker version 29.0.1, build eedd9698e9
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Check that Docker Compose is installed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-3-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker compose version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-4-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;Docker Compose version 2.40.3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Check that &lt;code&gt;curl&lt;/code&gt; is available (we need it for the push notification):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-5-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;curl --version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected output (first line is enough):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-6-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;curl 8.12.1 (x86_64-pc-linux-gnu) ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Check that you can access the crontab:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-7-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-7-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;crontab -l
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This should either print your current crontab or say &lt;code&gt;no crontab for &amp;lt;user&amp;gt;&lt;/code&gt;. Either is fine.&lt;/p&gt;
&lt;p&gt;Finally, confirm your Compose stacks are where you expect them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-8-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-8-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;ls ~/apps/*/docker-compose.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should see a path for each stack you manage.&lt;/p&gt;
&lt;h2 id="create-the-push-monitor-in-uptime-kuma"&gt;Create the Push Monitor in Uptime Kuma&lt;/h2&gt;
&lt;p&gt;Open your Uptime Kuma dashboard and click &lt;strong&gt;Add New Monitor&lt;/strong&gt;. Set the &lt;strong&gt;Monitor Type&lt;/strong&gt; to &lt;strong&gt;Push&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/update-automation/kuma-add-push-monitor.webp" alt="Add New Monitor: Monitor Type set to Push" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;Give it a name like &lt;code&gt;Weekly Updates&lt;/code&gt;. The important setting is the &lt;strong&gt;Heartbeat Interval&lt;/strong&gt;. Since we&amp;rsquo;re running our update once a week, we want Uptime Kuma to expect a heartbeat roughly every seven days. That&amp;rsquo;s the dead man&amp;rsquo;s switch interval, held open by each weekly ping. Set the interval to &lt;strong&gt;seven days plus one hour&lt;/strong&gt; (608,400 seconds). The extra hour means that if the update starts a few minutes late or takes longer than usual, Uptime Kuma won&amp;rsquo;t mark it as down straight away.&lt;/p&gt;
&lt;p&gt;Set &lt;strong&gt;Retries&lt;/strong&gt; to &lt;strong&gt;2&lt;/strong&gt; and &lt;strong&gt;Heartbeat Retry Interval&lt;/strong&gt; to &lt;strong&gt;3600&lt;/strong&gt; seconds (1 hour). If Kuma misses the expected heartbeat, it&amp;rsquo;ll wait and retry twice at one-hour intervals before marking the monitor as down. This avoids false alarms if the update script takes even longer or the push request gets lost.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/update-automation/kuma-push-interval-7d1h.webp" alt="Heartbeat interval set to 608400 seconds (7 days, 1 hour), 2 retries at 3600 seconds" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;After saving the monitor, Uptime Kuma shows you a push URL. It looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-9-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-9-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;https://your-uptime-kuma.example.com/api/push/REPLACE_WITH_YOUR_TOKEN?status=up&amp;amp;msg=OK&amp;amp;ping=
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Copy this URL. We&amp;rsquo;ll need it in the update script. The URL accepts three query parameters: &lt;code&gt;status&lt;/code&gt; (&lt;code&gt;up&lt;/code&gt; or &lt;code&gt;down&lt;/code&gt;), &lt;code&gt;msg&lt;/code&gt; (a short message), and &lt;code&gt;ping&lt;/code&gt; (response time in ms, which we leave empty). You can see how the endpoint works in the &lt;a href="https://github.com/louislam/uptime-kuma/blob/master/server/routers/api-router.js"&gt;source code&lt;/a&gt;. Anyone with the token can push status to the monitor, but they can&amp;rsquo;t read anything from your Kuma instance.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;IMPORTANT:&lt;/strong&gt; Keep it out of public repositories. Treat this like a password, keep it a secret!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="the-notifier-script-notify_kumash"&gt;The Notifier Script: &lt;code&gt;notify_kuma.sh&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;This script pushes a status (up or down) with a short message to your Uptime Kuma push monitor. We keep it as a separate file so we can reuse it for other cron jobs later.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#1"&gt; 1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#737994;font-style:italic"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#2"&gt; 2&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#737994;font-style:italic"&gt;# notify_kuma.sh: Push a status update to an Uptime Kuma push monitor.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#3"&gt; 3&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#4"&gt; 4&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#99d1db"&gt;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#5"&gt; 5&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#6"&gt; 6&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#f2d5cf"&gt;USAGE&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;Usage: notify_kuma.sh &amp;lt;up|down&amp;gt; &amp;lt;message&amp;gt; &amp;lt;push-url&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#7"&gt; 7&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="8"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#8"&gt; 8&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#f2d5cf"&gt;STATUS&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#a6d189"&gt;${&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;1&lt;/span&gt;:?&lt;span style="color:#f2d5cf"&gt;$USAGE&lt;/span&gt;&lt;span style="color:#a6d189"&gt;}&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="9"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#9"&gt; 9&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#f2d5cf"&gt;MSG&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#a6d189"&gt;${&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;2&lt;/span&gt;:?&lt;span style="color:#f2d5cf"&gt;$USAGE&lt;/span&gt;&lt;span style="color:#a6d189"&gt;}&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="10"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#10"&gt;10&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#f2d5cf"&gt;PUSH_URL&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#a6d189"&gt;${&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;3&lt;/span&gt;:?&lt;span style="color:#f2d5cf"&gt;$USAGE&lt;/span&gt;&lt;span style="color:#a6d189"&gt;}&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="11"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#11"&gt;11&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="12"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#12"&gt;12&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#737994;font-style:italic"&gt;# Simple &amp;#34;space to +&amp;#34; encoding for the query string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="13"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#13"&gt;13&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#f2d5cf"&gt;ENCODED_MSG&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#a6d189"&gt;${&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;MSG&lt;/span&gt;// /+&lt;span style="color:#a6d189"&gt;}&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="14"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#14"&gt;14&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="15"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#15"&gt;15&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#f2d5cf"&gt;FULL_URL&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#a6d189"&gt;${&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;PUSH_URL&lt;/span&gt;&lt;span style="color:#a6d189"&gt;}&lt;/span&gt;&lt;span style="color:#a6d189"&gt;?status=&lt;/span&gt;&lt;span style="color:#a6d189"&gt;${&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;STATUS&lt;/span&gt;&lt;span style="color:#a6d189"&gt;}&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;amp;msg=&lt;/span&gt;&lt;span style="color:#a6d189"&gt;${&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;ENCODED_MSG&lt;/span&gt;&lt;span style="color:#a6d189"&gt;}&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;amp;ping=&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="16"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#16"&gt;16&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="17"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#17"&gt;17&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#f2d5cf"&gt;HTTP_CODE&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#ca9ee6"&gt;$(&lt;/span&gt;curl -s -o /dev/null -w &lt;span style="color:#a6d189"&gt;&amp;#34;%{http_code}&amp;#34;&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$FULL_URL&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ca9ee6"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="18"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#18"&gt;18&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="19"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#19"&gt;19&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;if&lt;/span&gt; &lt;span style="color:#99d1db;font-weight:bold"&gt;[&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$HTTP_CODE&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt; -ne &lt;span style="color:#ef9f76"&gt;200&lt;/span&gt; &lt;span style="color:#99d1db;font-weight:bold"&gt;]&lt;/span&gt;; &lt;span style="color:#ca9ee6"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="20"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#20"&gt;20&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#99d1db"&gt;echo&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;WARNING: Uptime Kuma push failed with HTTP &lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$HTTP_CODE&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&amp;amp;&lt;span style="color:#ef9f76"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="21"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#21"&gt;21&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It takes three arguments: &lt;code&gt;status&lt;/code&gt; (either &lt;code&gt;up&lt;/code&gt; or &lt;code&gt;down&lt;/code&gt;), &lt;code&gt;msg&lt;/code&gt; (a short human-readable message), and the push &lt;code&gt;url&lt;/code&gt;. It does a space-to-&lt;code&gt;+&lt;/code&gt; encoding on the message so it works in the URL query string, then calls &lt;code&gt;curl&lt;/code&gt; and checks for an HTTP 200 response. If the push fails, it prints a warning to &lt;code&gt;stderr&lt;/code&gt;. You&amp;rsquo;ll see it in the cron logs if you decide to set up a log file.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ping=&lt;/code&gt; parameter is left empty. Uptime Kuma treats that as &amp;ldquo;no latency measurement&amp;rdquo;, which makes sense for a batch job. Feel free to drop it from the script if it feels unnecessary.&lt;/p&gt;
&lt;h2 id="the-update-script-updatesh"&gt;The Update Script: &lt;code&gt;update.sh&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;This script cleans up disk space, loops through your Compose stacks, pulls new images, restarts services, and calls the notifier at the end.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#1"&gt; 1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#737994;font-style:italic"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#2"&gt; 2&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#737994;font-style:italic"&gt;# update.sh: Pull and restart Docker Compose stacks, then notify Uptime Kuma.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#3"&gt; 3&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#4"&gt; 4&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#f2d5cf"&gt;SCRIPT_DIR&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ca9ee6"&gt;$(&lt;/span&gt;&lt;span style="color:#99d1db"&gt;cd&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ca9ee6"&gt;$(&lt;/span&gt;dirname &lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#a6d189"&gt;${&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;BASH_SOURCE&lt;/span&gt;[0]&lt;span style="color:#a6d189"&gt;}&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ca9ee6"&gt;)&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#99d1db;font-weight:bold"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#99d1db"&gt;pwd&lt;/span&gt;&lt;span style="color:#ca9ee6"&gt;)&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#5"&gt; 5&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#6"&gt; 6&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#f2d5cf"&gt;KUMA_URL&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;https://your-uptime-kuma.example.com/api/push/REPLACE_WITH_YOUR_TOKEN&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#7"&gt; 7&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="8"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#8"&gt; 8&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#737994;font-style:italic"&gt;# Add your Compose stack directories here, one per line.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="9"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#9"&gt; 9&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#f2d5cf"&gt;APPS&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="10"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#10"&gt;10&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;/home/&amp;lt;user&amp;gt;/apps/paperless-ngx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="11"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#11"&gt;11&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;/home/&amp;lt;user&amp;gt;/apps/it-tools&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="12"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#12"&gt;12&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#737994;font-style:italic"&gt;# &amp;#34;/home/&amp;lt;user&amp;gt;/apps/another-stack&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="13"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#13"&gt;13&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="14"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#14"&gt;14&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="15"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#15"&gt;15&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#99d1db"&gt;echo&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;Pruning unused Docker resources...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="16"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#16"&gt;16&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker system prune -f
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="17"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#17"&gt;17&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker image prune -af
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="18"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#18"&gt;18&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#99d1db"&gt;echo&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;Docker cleanup complete.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="19"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#19"&gt;19&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="20"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#20"&gt;20&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#f2d5cf"&gt;FAILED&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#ef9f76"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="21"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#21"&gt;21&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="22"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#22"&gt;22&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;for&lt;/span&gt; APP in &lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#a6d189"&gt;${&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;APPS&lt;/span&gt;[@]&lt;span style="color:#a6d189"&gt;}&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;; &lt;span style="color:#ca9ee6"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="23"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#23"&gt;23&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#99d1db"&gt;echo&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;Updating: &lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$APP&lt;/span&gt;&lt;span style="color:#a6d189"&gt;...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="24"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#24"&gt;24&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#99d1db"&gt;cd&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$APP&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#99d1db;font-weight:bold"&gt;||&lt;/span&gt; &lt;span style="color:#99d1db;font-weight:bold"&gt;{&lt;/span&gt; &lt;span style="color:#99d1db"&gt;echo&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;ERROR: Could not cd into &lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$APP&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&amp;amp;2; &lt;span style="color:#f2d5cf"&gt;FAILED&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;1; &lt;span style="color:#ca9ee6"&gt;continue&lt;/span&gt;; &lt;span style="color:#99d1db;font-weight:bold"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="25"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#25"&gt;25&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="26"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#26"&gt;26&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;if&lt;/span&gt; docker compose pull &lt;span style="color:#99d1db;font-weight:bold"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker compose up -d; &lt;span style="color:#ca9ee6"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="27"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#27"&gt;27&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#99d1db"&gt;echo&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;OK: &lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$APP&lt;/span&gt;&lt;span style="color:#a6d189"&gt; updated successfully&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="28"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#28"&gt;28&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="29"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#29"&gt;29&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#99d1db"&gt;echo&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;FAIL: &lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$APP&lt;/span&gt;&lt;span style="color:#a6d189"&gt; had errors&amp;#34;&lt;/span&gt; &amp;gt;&amp;amp;&lt;span style="color:#ef9f76"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="30"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#30"&gt;30&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#f2d5cf"&gt;FAILED&lt;/span&gt;&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#ef9f76"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="31"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#31"&gt;31&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="32"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#32"&gt;32&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="33"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#33"&gt;33&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="34"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#34"&gt;34&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;if&lt;/span&gt; &lt;span style="color:#99d1db;font-weight:bold"&gt;[&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$FAILED&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt; -eq &lt;span style="color:#ef9f76"&gt;0&lt;/span&gt; &lt;span style="color:#99d1db;font-weight:bold"&gt;]&lt;/span&gt;; &lt;span style="color:#ca9ee6"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="35"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#35"&gt;35&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$SCRIPT_DIR&lt;/span&gt;&lt;span style="color:#a6d189"&gt;/notify_kuma.sh&amp;#34;&lt;/span&gt; up &lt;span style="color:#a6d189"&gt;&amp;#34;All stacks updated successfully&amp;#34;&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$KUMA_URL&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="36"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#36"&gt;36&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="37"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#37"&gt;37&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$SCRIPT_DIR&lt;/span&gt;&lt;span style="color:#a6d189"&gt;/notify_kuma.sh&amp;#34;&lt;/span&gt; down &lt;span style="color:#a6d189"&gt;&amp;#34;One or more stacks failed to update&amp;#34;&lt;/span&gt; &lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#f2d5cf"&gt;$KUMA_URL&lt;/span&gt;&lt;span style="color:#a6d189"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="38"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#38"&gt;38&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here&amp;rsquo;s each part in more detail:&lt;/p&gt;
&lt;h3 id="disk-cleanup"&gt;Disk Cleanup&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;docker system prune -f&lt;/code&gt; removes stopped containers, unused networks, dangling images, and unused build cache.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker image prune -af&lt;/code&gt; then removes &lt;strong&gt;all&lt;/strong&gt; unused images (not only dangling ones) on a weekly schedule, old image tags build up and this reclaims the space.&lt;/p&gt;
&lt;p&gt;You will re-download layers if you later &lt;code&gt;docker run&lt;/code&gt; an older tag, but for a weekly homelab job that&amp;rsquo;s a reasonable trade-off. The &lt;a href="https://docs.docker.com/engine/manage-resources/pruning/"&gt;Docker documentation on pruning&lt;/a&gt; covers this in more detail.&lt;/p&gt;
&lt;h3 id="the-apps-array"&gt;The &lt;code&gt;APPS&lt;/code&gt; Array&lt;/h3&gt;
&lt;p&gt;Each entry is an absolute path to a directory containing a &lt;code&gt;docker-compose.yaml&lt;/code&gt;. Edit this list to match your setup.&lt;/p&gt;
&lt;h3 id="the-update-loop"&gt;The Update Loop&lt;/h3&gt;
&lt;p&gt;For each app, the script &lt;code&gt;cd&lt;/code&gt;s into the directory (the &lt;code&gt;||&lt;/code&gt; means a missing directory won&amp;rsquo;t stop the whole script), then runs &lt;code&gt;docker compose pull &amp;amp;&amp;amp; docker compose up -d&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; between &lt;code&gt;pull&lt;/code&gt; and &lt;code&gt;up -d&lt;/code&gt; matters: if the pull fails (say, a network timeout), we don&amp;rsquo;t restart the containers with stale images. If something fails, the &lt;code&gt;FAILED&lt;/code&gt; flag gets set, but the loop keeps going. A problem with one stack doesn&amp;rsquo;t stop the others from updating.&lt;/p&gt;
&lt;h3 id="the-notification"&gt;The Notification&lt;/h3&gt;
&lt;p&gt;After the loop finishes, the script calls &lt;code&gt;notify_kuma.sh&lt;/code&gt; once. It sends &lt;code&gt;up&lt;/code&gt; if everything worked, &lt;code&gt;down&lt;/code&gt; if anything failed.&lt;/p&gt;
&lt;p&gt;Make both scripts executable, and restrict &lt;code&gt;update.sh&lt;/code&gt; your user only since it contains the push URL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-10-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;chmod +x ~/apps/update.sh ~/apps/notify_kuma.sh
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-10-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-10-2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span&gt;chmod &lt;span style="color:#ef9f76"&gt;700&lt;/span&gt; ~/apps/update.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For most homelab setups this is sufficient. If you want to go stricter, you could pull the push URL from a secret manager at runtime instead of hardcoding it in the script. We mention &lt;a href="#a-note-on-secret-management"&gt;phase.dev for secret management&lt;/a&gt; later in this article as an option.&lt;/p&gt;
&lt;h2 id="scheduling-with-cron"&gt;Scheduling With Cron&lt;/h2&gt;
&lt;p&gt;Add two entries to your crontab with &lt;code&gt;crontab -e&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-11-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;7 3 * * 0 /home/&amp;lt;user&amp;gt;/apps/update.sh &amp;gt;&amp;gt; /home/&amp;lt;user&amp;gt;/apps/update.log 2&amp;gt;&amp;amp;1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-11-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-11-2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span&gt;41 3 * * 0 /home/&amp;lt;user&amp;gt;/apps/update.sh &amp;gt;&amp;gt; /home/&amp;lt;user&amp;gt;/apps/update.log 2&amp;gt;&amp;amp;1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This runs the update script every Sunday at 03:07, and again at 03:41. We use odd minutes (7 and 41 instead of 0 and 30) so the job doesn&amp;rsquo;t fire at the same time as &lt;code&gt;logrotate&lt;/code&gt;, backup scripts, or anything else pinned to the top or bottom of the hour. It&amp;rsquo;s a small thing, but it avoids timing collisions. Use any number you wish, or stick to clean multiples of half-hours, if that&amp;rsquo;s your preference.&lt;/p&gt;
&lt;p&gt;The duplicate entry about half an hour later is there because stacks sometimes fail on the first attempt for reasons that resolve on their own. For example: a transient registry timeout, a sibling container still starting up. Running it again a bit later catches those, and when the first run already succeeded, the second one does nothing. Images are already pulled, containers are already running.&lt;/p&gt;
&lt;p&gt;Both runs append to &lt;code&gt;update.log&lt;/code&gt;. This log grows over time. You can configure &lt;code&gt;logrotate&lt;/code&gt; for it, or just truncate it manually with &lt;code&gt;truncate -s 0 ~/apps/update.log&lt;/code&gt; when needed. If you don&amp;rsquo;t want to look at the logs for this script (I don&amp;rsquo;t), you can leave out the &lt;code&gt;&amp;gt;&amp;gt; /.../update.log 2&amp;gt;&amp;amp;1&lt;/code&gt; bit.&lt;/p&gt;
&lt;h2 id="how-it-looks-in-practice"&gt;How It Looks In Practice&lt;/h2&gt;
&lt;p&gt;Our Uptime Kuma dashboard shows the &lt;code&gt;Weekly Updates&lt;/code&gt; monitoring group with its heartbeat history. Green bars are successful runs, orange ones are pending or waiting for retries, and red means retry window exceeded and it&amp;rsquo;s marked a proper failure with notification. You can see all three states at a glance.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/update-automation/kuma-dashboard-green.webp" alt="Weekly Docker Updates monitor: green, orange, and red heartbeat bars" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;If you have notifications configured in Uptime Kuma (Discord, Telegram, email, ntfy, Apprise, and many others), you&amp;rsquo;ll get an alert when the monitor goes down. You will be notified about failures, and the first success after a failure.&lt;/p&gt;
&lt;h2 id="a-note-on-secret-management"&gt;A Note on Secret Management&lt;/h2&gt;
&lt;p&gt;You might not want &lt;code&gt;.env&lt;/code&gt; files sitting next to your Compose files. One option is &lt;a href="https://phase.dev"&gt;Phase&lt;/a&gt;, an open-source secret manager (&lt;a href="https://github.com/phasehq/console"&gt;source on GitHub&lt;/a&gt;). You can prefix your Compose commands with &lt;code&gt;phase run&lt;/code&gt; to inject secrets as environment variables at runtime:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-12-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-12-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;phase run --env prod docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This keeps secrets out of the filesystem. We&amp;rsquo;ll cover setting up Phase in a dedicated article.&lt;/p&gt;
&lt;p&gt;For an open-source audit of Uptime Kuma (telemetry, licence, push endpoint behaviour) and ways to support the project, see our &lt;a href="https://coderscompass.org/articles/uptime-kuma-self-hosted-monitoring-tool/"&gt;Uptime Kuma article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The whole setup is two scripts, a couple cron entries, and a push monitor. Your Docker Compose stacks get pulled and restarted weekly, and you only hear about it when something goes wrong.&lt;/p&gt;
&lt;p&gt;Let us know if you&amp;rsquo;ve done something similar. Like per-stack backups before updating, Slack notifications, or used a different monitoring tool.&lt;/p&gt;</content:encoded><category>self-hosting</category><category>docker</category><category>utilities</category><category>developer-tools</category><category>uptime-monitoring</category></item><item><title>Uptime Kuma: Self-Hosted Monitoring Tool</title><link>https://coderscompass.org/articles/uptime-kuma-self-hosted-monitoring-tool/</link><guid isPermaLink="true">https://coderscompass.org/articles/uptime-kuma-self-hosted-monitoring-tool/</guid><pubDate>Sun, 19 Apr 2026 12:00:00 +0530</pubDate><dc:creator>Subhomoy Haldar</dc:creator><description>Self-host Uptime Kuma, a monitoring tool that tracks your services with HTTP, TCP, DNS, and push monitors. Notifications via email, Discord, Telegram, and over 50 other methods.</description><content:encoded>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/louislam/uptime-kuma"&gt;Uptime Kuma&lt;/a&gt; is a self-hosted monitoring tool. You point it at your services, and it checks whether they’re up. It can check via HTTP, TCP, DNS, ping, or push. If something goes down, it sends you a notification through whichever channel you prefer: email, Discord, Telegram, Slack, ntfy, and &lt;a href="https://github.com/louislam/uptime-kuma/tree/master/server/notification-providers"&gt;many others&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The project is created by &lt;a href="https://github.com/louislam"&gt;Louis Lam&lt;/a&gt; and is available on &lt;a href="https://github.com/louislam/uptime-kuma"&gt;GitHub&lt;/a&gt; under the MIT licence. The latest release at the time of writing is &lt;a href="https://github.com/louislam/uptime-kuma/releases/tag/2.3.0"&gt;v2.3.0&lt;/a&gt;, published on 1 May 2026.&lt;/p&gt;
&lt;p&gt;If you run any self-hosted services, you probably want to know when they go down before your users do. Uptime Kuma handles that with a clean dashboard and a deployment that takes five minutes or fewer.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-started/get-docker/"&gt;Docker&lt;/a&gt; version 20.10 or later with the Compose plugin.&lt;/li&gt;
&lt;li&gt;A Linux server (Debian or Ubuntu recommended) or any system capable of running Docker containers.&lt;/li&gt;
&lt;li&gt;An optional &lt;a href="https://en.wikipedia.org/wiki/Reverse_proxy"&gt;reverse proxy&lt;/a&gt; if you want to expose it securely (e.g., &lt;a href="https://caddyserver.com/"&gt;Caddy&lt;/a&gt;, &lt;a href="https://traefik.io/"&gt;Traefik&lt;/a&gt;, or &lt;a href="https://nginx.org/"&gt;Nginx&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="verification"&gt;Verification&lt;/h2&gt;
&lt;p&gt;Before proceeding with the installation, verify that Docker is installed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker --version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should see output similar to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-1-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;Docker version 29.0.1, build eedd9698e9
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Verify Docker Compose is installed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-2-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker compose version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-3-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;Docker Compose version 2.40.3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;Choose or create an appropriate directory, such as &lt;code&gt;~/apps/uptimekuma&lt;/code&gt;. Some folks prefer the &lt;code&gt;/opt/docker/&amp;lt;appname&amp;gt;&lt;/code&gt; pattern.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;docker-compose.yaml&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#1"&gt; 1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#2"&gt; 2&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;uptimekuma&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#3"&gt; 3&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;container_name&lt;/span&gt;: uptimekuma
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#4"&gt; 4&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;image&lt;/span&gt;: docker.io/louislam/uptime-kuma:2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#5"&gt; 5&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;restart&lt;/span&gt;: unless-stopped
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#6"&gt; 6&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#7"&gt; 7&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - caddy-network
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="8"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#8"&gt; 8&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="9"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#9"&gt; 9&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - &lt;span style="color:#a6d189"&gt;&amp;#34;3001:3001&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="10"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#10"&gt;10&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="11"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#11"&gt;11&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - ./data:/app/data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="12"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#12"&gt;12&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#737994;font-style:italic"&gt;# Uncomment to enable the Docker Container monitor type.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="13"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#13"&gt;13&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#737994;font-style:italic"&gt;# - /var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="14"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#14"&gt;14&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;logging&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="15"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#15"&gt;15&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;driver&lt;/span&gt;: &lt;span style="color:#a6d189"&gt;&amp;#34;json-file&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="16"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#16"&gt;16&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;options&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="17"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#17"&gt;17&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;max-size&lt;/span&gt;: &lt;span style="color:#a6d189"&gt;&amp;#34;1m&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="18"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#18"&gt;18&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;max-file&lt;/span&gt;: &lt;span style="color:#a6d189"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="19"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#19"&gt;19&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="20"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#20"&gt;20&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="21"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#21"&gt;21&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;caddy-network&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="22"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#22"&gt;22&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;external&lt;/span&gt;: &lt;span style="color:#ef9f76"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The default port is 3001. Change the port mapping if that&amp;rsquo;s already in use on your server. If you&amp;rsquo;re using a reverse proxy like Caddy, you can remove the &lt;code&gt;ports&lt;/code&gt; section entirely and route traffic through the Docker network instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pull the image and start the container:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-4-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker compose pull &lt;span style="color:#99d1db;font-weight:bold"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Access Uptime Kuma at &lt;code&gt;http://localhost:3001&lt;/code&gt; (or your configured address). On first launch, you&amp;rsquo;ll be asked to create an admin account.&lt;/p&gt;
&lt;h2 id="https"&gt;HTTPS&lt;/h2&gt;
&lt;h3 id="on-a-private-tailscale-network"&gt;On a Private Tailscale Network&lt;/h3&gt;
&lt;p&gt;If you want free HTTPS without configuring certificates manually, you can use &lt;a href="https://tailscale.com/kb/1242/tailscale-serve"&gt;Tailscale Serve&lt;/a&gt;. After installing Tailscale on your server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-5-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;tailscale serve --bg --https&lt;span style="color:#99d1db;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#ef9f76"&gt;3001&lt;/span&gt; localhost:3001
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This exposes your Uptime Kuma instance over HTTPS on your Tailscale network. Omit the external caddy network if you do not plan to expose it publicly behind a reverse proxy like Caddy.&lt;/p&gt;
&lt;h3 id="on-the-public-internet-with-caddy"&gt;On the Public Internet With Caddy&lt;/h3&gt;
&lt;p&gt;If you want to access Uptime Kuma publicly, use HTTPS. You can achieve this with a reverse proxy like Caddy, especially if you have it &lt;a href="https://github.com/Coders-Compass/caddy-with-ratelimit/"&gt;configured with the rate-limit plugin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;docker-compose.yaml&lt;/code&gt; above already includes a &lt;code&gt;caddy-network&lt;/code&gt; for this purpose. Point your Caddy configuration at the &lt;code&gt;uptimekuma&lt;/code&gt; container on port 3001.&lt;/p&gt;
&lt;h2 id="monitor-types"&gt;Monitor Types&lt;/h2&gt;
&lt;p&gt;When you click &lt;strong&gt;Add New Monitor&lt;/strong&gt;, the default type is &lt;strong&gt;HTTP(s)&lt;/strong&gt;. It sends a &lt;code&gt;GET&lt;/code&gt; request to a URL and checks the response code.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/uptime-kuma/kuma-add-monitor-http.webp" alt="Add New Monitor dialog with HTTP(s) selected" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;Uptime Kuma supports several other monitor types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTP(s)&lt;/strong&gt;: checks a URL for a specific status code or keyword in the response body.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP&lt;/strong&gt;: connects to a host and port. Useful for databases, mail servers, or anything that listens on a port. We use this to &lt;a href="https://coderscompass.org/articles/monitoring-ssh-reachability-with-uptime-kuma-and-tailscale/"&gt;keep tabs on SSH across a Tailscale tailnet&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ping&lt;/strong&gt;: ICMP ping to check whether a host is reachable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DNS&lt;/strong&gt;: queries a DNS server and checks the result against an expected value.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Push&lt;/strong&gt;: the monitored service pushes a heartbeat to Uptime Kuma instead of the other way around. Useful for cron jobs and batch scripts. We use this for &lt;a href="https://coderscompass.org/articles/automating-docker-compose-updates-with-uptime-kuma/"&gt;a dead man&amp;rsquo;s switch on weekly Docker Compose updates&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker Container&lt;/strong&gt;: monitors whether a specific container is running via the Docker socket.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Steam Game Server&lt;/strong&gt;: checks game server availability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MQTT&lt;/strong&gt;: subscribes to an MQTT topic and watches for messages.&lt;/li&gt;
&lt;li&gt;And more! The full list of types is in the &lt;a href="https://github.com/louislam/uptime-kuma/tree/master/server/monitor-types"&gt;&lt;code&gt;server/monitor-types/&lt;/code&gt;&lt;/a&gt; directory in the source.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each monitor can have its own check interval, retries, and notification settings.&lt;/p&gt;
&lt;h2 id="notifications"&gt;Notifications&lt;/h2&gt;
&lt;p&gt;Uptime Kuma supports over 50 notification methods. You can configure multiple notification channels per monitor or set up default notifications that apply to all monitors.&lt;/p&gt;
&lt;p&gt;To add a notification method, go to &lt;strong&gt;Settings &amp;gt; Notifications &amp;gt; Setup Notification&lt;/strong&gt; (or click &lt;strong&gt;Set Up Notification&lt;/strong&gt; when creating a monitor).&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example using SMTP email:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/uptime-kuma/kuma-notification-smtp.webp" alt="Adding an SMTP email notification" loading="lazy" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;Fill in your SMTP server details (host, port, username, password) and the sender/recipient addresses. Click &lt;strong&gt;Test&lt;/strong&gt; to send a test notification and confirm it works before saving.&lt;/p&gt;
&lt;p&gt;Other popular options include Discord webhooks, Telegram bots, Slack incoming webhooks, ntfy, Apprise, Gotify, PagerDuty, and Pushover. The full list is visible in the notification type dropdown.&lt;/p&gt;
&lt;h2 id="status-pages"&gt;Status Pages&lt;/h2&gt;
&lt;p&gt;Uptime Kuma can generate public or internal status pages that show the current state of your monitors. This is useful if you want to share uptime information with users or a team without giving them access to the full dashboard.&lt;/p&gt;
&lt;p&gt;You can create status pages from &lt;strong&gt;Status Pages&lt;/strong&gt; in the top navigation bar. Each page gets its own URL and can include whichever monitors you choose.&lt;/p&gt;
&lt;p&gt;One limitation: visitors can&amp;rsquo;t subscribe to status page updates. On services like Atlassian Statuspage, users can sign up for email or SMS alerts when something changes. Uptime Kuma doesn&amp;rsquo;t have this yet. It&amp;rsquo;s been &lt;a href="https://github.com/louislam/uptime-kuma/issues/916"&gt;requested&lt;/a&gt; &lt;a href="https://github.com/louislam/uptime-kuma/issues/1911"&gt;several&lt;/a&gt; &lt;a href="https://github.com/louislam/uptime-kuma/issues/5537"&gt;times&lt;/a&gt; but hasn&amp;rsquo;t been implemented. For now, notifications only go to channels configured by the admin, not to end users directly.&lt;/p&gt;
&lt;h2 id="open-source-audit"&gt;Open-Source Audit&lt;/h2&gt;
&lt;p&gt;Similar to what we did in our &lt;a href="https://coderscompass.org/articles/it-tools-self-hosted-collection-of-developer-tools-and-it-utilities/#analytics-on-the-public-instance"&gt;IT-Tools article&lt;/a&gt;, we checked what Uptime Kuma does on the server side. Each claim below links to a specific file in the repository at the v2.3.0 tag.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Licence and authorship.&lt;/strong&gt; Uptime Kuma is MIT-licensed. The primary maintainer is &lt;a href="https://github.com/louislam"&gt;Louis Lam&lt;/a&gt;, with additional area maintainers. The latest release, &lt;a href="https://github.com/louislam/uptime-kuma/releases/tag/2.3.0"&gt;v2.3.0&lt;/a&gt;, was published on 1 May 2026, and the project sees frequent merges from contributors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No outbound telemetry by default.&lt;/strong&gt; The server-side code under &lt;a href="https://github.com/louislam/uptime-kuma/tree/2.3.0/server"&gt;&lt;code&gt;server/&lt;/code&gt;&lt;/a&gt; doesn&amp;rsquo;t phone home. We found no evidence of analytics SDKs, &lt;code&gt;fetch&lt;/code&gt; calls to external services, or tracking endpoints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update check is client-side only.&lt;/strong&gt; The &amp;ldquo;new version available&amp;rdquo; banner is a client-side fetch against the GitHub Releases API. Your server doesn&amp;rsquo;t call out to check for updates. It&amp;rsquo;s just informational for whoever is viewing the dashboard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Push endpoint behaviour.&lt;/strong&gt; The route handler for &lt;code&gt;/api/push/:pushToken&lt;/code&gt; confirms the URL format used by push monitors. The token in the URL is the only credential. No additional authentication is applied.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If any of this changed (say, telemetry was quietly introduced) the community would notice and fork. Same trust model as any open-source self-hosted tool.&lt;/p&gt;
&lt;h2 id="supporting-the-project"&gt;Supporting the Project&lt;/h2&gt;
&lt;p&gt;If you get value out of Uptime Kuma, consider giving back:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sponsor Louis Lam&lt;/strong&gt; on &lt;a href="https://github.com/sponsors/louislam"&gt;GitHub Sponsors&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contribute on GitHub&lt;/strong&gt;: the project is written in Node.js and Vue.js. Bug reports, feature requests, and pull requests are welcome at the &lt;a href="https://github.com/louislam/uptime-kuma"&gt;repository&lt;/a&gt;. Make sure to read the &lt;a href="https://github.com/louislam/uptime-kuma/blob/master/CODE_OF_CONDUCT.md"&gt;code of conduct&lt;/a&gt; and &lt;a href="https://github.com/louislam/uptime-kuma/blob/master/CONTRIBUTING.md"&gt;contributing guide&lt;/a&gt; before contributing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Help with translations&lt;/strong&gt;: Uptime Kuma supports many languages and translation contributions are always appreciated.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="practical-applications"&gt;Practical Applications&lt;/h2&gt;
&lt;p&gt;Congratulations on setting up Uptime Kuma! Now, what can you actually do with it? A few usages have earned their own articles on this site, with more on the way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://coderscompass.org/articles/monitoring-ssh-reachability-with-uptime-kuma-and-tailscale/"&gt;Monitoring SSH reachability across a Tailscale tailnet&lt;/a&gt;&lt;/strong&gt;: a TCP Port monitor on port 22 for every machine, with notes on upside-down monitoring for accidental public exposure, and on out-of-band fallback paths for the day Tailscale (or your local network) is the problem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://coderscompass.org/articles/automating-docker-compose-updates-with-uptime-kuma/"&gt;A dead man’s switch for Docker Compose updates&lt;/a&gt;&lt;/strong&gt;: a push-monitor pattern that triggers an alert when cron goes silent. Weekly &lt;code&gt;docker compose pull &amp;amp;&amp;amp; docker compose up -d&lt;/code&gt; across your stacks, with a single notification when something fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A few more applications we use ourselves but haven’t yet written up, each likely worth its own walk-through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Status pages for self-hosted services shared with family or a small group, despite the subscription limitation noted in the previous section.&lt;/li&gt;
&lt;li&gt;A “are my backups still running?” push monitor pointed at the success-tail of your backup script, with a heartbeat window matched to the backup cadence.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let us know in the comments if any of these are worth reading, and we’ll prioritise them.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Uptime Kuma gives you a self-hosted monitoring dashboard with a five-minute Docker deployment. It covers the common monitoring patterns (HTTP, TCP, DNS, ping, push), has a wide range of notification integrations, and can generate status pages.&lt;/p&gt;
&lt;p&gt;Have you set up Uptime Kuma or another monitoring tool? Share your experience in the comments.&lt;/p&gt;</content:encoded><category>self-hosting</category><category>docker</category><category>utilities</category><category>uptime-monitoring</category><category>tailscale</category></item><item><title>SearXNG: Self-Hosted Private Search Aggregator</title><link>https://coderscompass.org/articles/searxng-self-hosted-private-search-aggregator/</link><guid isPermaLink="true">https://coderscompass.org/articles/searxng-self-hosted-private-search-aggregator/</guid><pubDate>Mon, 19 Jan 2026 17:57:30 +0530</pubDate><dc:creator>Subhomoy Haldar</dc:creator><description>Self-host SearXNG, a metasearch engine aggregating results from multiple engines. Ad-free results and JSON API for LLM integration.</description><content:encoded>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/searxng/searxng"&gt;SearXNG&lt;/a&gt; is a free, open-source metasearch engine that aggregates results from over 70 search engines while respecting your privacy. Unlike traditional search engines, SearXNG doesn’t track your searches, build a profile on you, or serve you advertisements.&lt;/p&gt;
&lt;p&gt;When you use popular search engines like Google or Bing, your queries are logged, analysed, and used to build an advertising profile. Even privacy-focused alternatives like DuckDuckGo are proprietary and require trust in a third party. By self-hosting SearXNG, you take complete control over your search experience. Your queries stay on your server, and you can verify exactly what the software does by examining the &lt;a href="https://github.com/searxng/searxng"&gt;source code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One particularly useful feature is SearXNG’s ability to output results as JSON. This makes it an excellent tool for integrating web search capabilities into local LLM applications like Open WebUI, with no need for API keys or worrying about rate limits.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-started/get-docker/"&gt;Docker&lt;/a&gt; version 20.10 or later installed on your system. Newer versions of Docker include the Docker Compose plugin.&lt;/li&gt;
&lt;li&gt;A Linux server (Debian or Ubuntu recommended) or any system capable of running Docker containers.&lt;/li&gt;
&lt;li&gt;Basic familiarity with the terminal.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="verification"&gt;Verification&lt;/h2&gt;
&lt;p&gt;Before proceeding with the installation, verify that Docker is installed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker --version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should see output similar to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-1-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;Docker version 29.0.1, build eedd9698e9
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Verify Docker Compose is installed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-2-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker compose version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-3-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;Docker Compose version 2.40.3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;SearXNG uses Redis (or Valkey, a Redis-compatible alternative) for cacheing and rate limiting. The Docker Compose configuration below sets up both services.&lt;/p&gt;
&lt;p&gt;Choose or create an appropriate directory, such as &lt;code&gt;~/self-hosting/searxng&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;docker-compose.yaml&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#1"&gt; 1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#2"&gt; 2&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;redis&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#3"&gt; 3&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;container_name&lt;/span&gt;: redis
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#4"&gt; 4&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;image&lt;/span&gt;: docker.io/valkey/valkey:8-alpine
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#5"&gt; 5&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;command&lt;/span&gt;: &amp;gt;-&lt;span style="color:#737994"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#6"&gt; 6&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#737994"&gt; valkey-server --save 30 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#7"&gt; 7&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#737994"&gt; --loglevel warning&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="8"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#8"&gt; 8&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;restart&lt;/span&gt;: unless-stopped
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="9"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#9"&gt; 9&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="10"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#10"&gt;10&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - searxng
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="11"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#11"&gt;11&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="12"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#12"&gt;12&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - ./data/redis:/data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="13"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#13"&gt;13&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;user&lt;/span&gt;: &lt;span style="color:#a6d189"&gt;&amp;#34;1000:1000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="14"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#14"&gt;14&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;cap_drop&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="15"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#15"&gt;15&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - ALL
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="16"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#16"&gt;16&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;cap_add&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="17"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#17"&gt;17&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - SETGID
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="18"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#18"&gt;18&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - SETUID
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="19"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#19"&gt;19&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - DAC_OVERRIDE
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="20"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#20"&gt;20&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;logging&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="21"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#21"&gt;21&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;driver&lt;/span&gt;: &lt;span style="color:#a6d189"&gt;&amp;#34;json-file&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="22"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#22"&gt;22&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;options&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="23"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#23"&gt;23&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;max-size&lt;/span&gt;: &lt;span style="color:#a6d189"&gt;&amp;#34;1m&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="24"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#24"&gt;24&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;max-file&lt;/span&gt;: &lt;span style="color:#a6d189"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="25"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#25"&gt;25&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="26"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#26"&gt;26&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;searxng&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="27"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#27"&gt;27&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;container_name&lt;/span&gt;: searxng
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="28"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#28"&gt;28&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;image&lt;/span&gt;: docker.io/searxng/searxng:latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="29"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#29"&gt;29&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;restart&lt;/span&gt;: unless-stopped
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="30"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#30"&gt;30&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="31"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#31"&gt;31&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - searxng
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="32"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#32"&gt;32&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="33"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#33"&gt;33&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - &lt;span style="color:#a6d189"&gt;&amp;#34;8080:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="34"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#34"&gt;34&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="35"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#35"&gt;35&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - ./data/searxng:/etc/searxng:rw
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="36"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#36"&gt;36&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="37"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#37"&gt;37&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - SEARXNG_BASE_URL=http://localhost:8080/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="38"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#38"&gt;38&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - UWSGI_WORKERS=2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="39"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#39"&gt;39&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - UWSGI_THREADS=2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="40"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#40"&gt;40&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;cap_drop&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="41"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#41"&gt;41&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - ALL
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="42"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#42"&gt;42&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;cap_add&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="43"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#43"&gt;43&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - CHOWN
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="44"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#44"&gt;44&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - SETGID
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="45"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#45"&gt;45&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - SETUID
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="46"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#46"&gt;46&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;logging&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="47"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#47"&gt;47&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;driver&lt;/span&gt;: &lt;span style="color:#a6d189"&gt;&amp;#34;json-file&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="48"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#48"&gt;48&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;options&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="49"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#49"&gt;49&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;max-size&lt;/span&gt;: &lt;span style="color:#a6d189"&gt;&amp;#34;1m&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="50"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#50"&gt;50&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;max-file&lt;/span&gt;: &lt;span style="color:#a6d189"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="51"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#51"&gt;51&lt;/a&gt;&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="52"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#52"&gt;52&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="53"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#53"&gt;53&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;searxng&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Adjust the &lt;code&gt;SEARXNG_BASE_URL&lt;/code&gt; environment variable to match your server’s IP address or hostname. If you’re deploying on a local network, use your server’s IP (e.g., &lt;code&gt;http://192.168.0.90:8080/&lt;/code&gt;). The port mapping can also be customised—change &lt;code&gt;8080:8080&lt;/code&gt; to something like &lt;code&gt;8090:8080&lt;/code&gt; if port 8080 is already in use.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pull the images and start the containers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-4-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker compose pull &lt;span style="color:#99d1db;font-weight:bold"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Access SearXNG at &lt;code&gt;http://localhost:8080&lt;/code&gt; (or your configured address).&lt;/p&gt;
&lt;h2 id="https"&gt;HTTPS&lt;/h2&gt;
&lt;h3 id="on-private-tailscale-network"&gt;On Private Tailscale Network&lt;/h3&gt;
&lt;p&gt;If you want free HTTPS without configuring certificates manually, you can use &lt;a href="https://tailscale.com/kb/1242/tailscale-serve"&gt;Tailscale Serve&lt;/a&gt;. After installing Tailscale on your server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-5-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;tailscale serve --bg &lt;span style="color:#ef9f76"&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This exposes your SearXNG instance over HTTPS on your Tailscale network.&lt;/p&gt;
&lt;h3 id="on-the-public-internet-with-caddy"&gt;On the Public Internet With Caddy&lt;/h3&gt;
&lt;p&gt;If you want to access your SearXNG instance publicly, it is &lt;strong&gt;strongly&lt;/strong&gt; recommended that you use HTTPS. You can achieve this with a reverse proxy like Caddy, especially if you have it &lt;a href="https://github.com/Coders-Compass/caddy-with-ratelimit/"&gt;configured with the rate-limit plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="features-and-use-cases"&gt;Features and Use Cases&lt;/h2&gt;
&lt;p&gt;SearXNG offers several interesting advantages over traditional search engines.&lt;/p&gt;
&lt;h3 id="ad-free-search-results"&gt;Ad-Free Search Results&lt;/h3&gt;
&lt;p&gt;All search results pass through your server, which means you see the actual results with no sponsored content or advertisements. This alone makes self-hosting worthwhile. You do not need to scroll past &amp;ldquo;promoted results&amp;rdquo; to find what you&amp;rsquo;re actually looking for.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/searxng/sample-ad-free-search.webp" alt="Ad-free search results showing multiple engine sources" decoding="async"&gt;
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Search results aggregated from Google, Qwant, Startpage, and DuckDuckGo. Completely ad-free.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="llm-integration-via-json-api"&gt;LLM Integration via JSON API&lt;/h3&gt;
&lt;p&gt;SearXNG can output search results as JSON, making it trivial to integrate with local LLM applications. If you’re running &lt;a href="https://github.com/open-webui/open-webui"&gt;Open WebUI&lt;/a&gt; or similar tools, you can use SearXNG as a web search backend without:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Paying for API access to search providers&lt;/li&gt;
&lt;li&gt;Managing API keys&lt;/li&gt;
&lt;li&gt;Hitting rate limits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can perform a (practically) unlimited number of searches through your own instance. The JSON output (look up how to enable it) includes useful metadata like which engines returned each result, relevance scores, and content snippets.&lt;/p&gt;
&lt;h3 id="aggregated-multi-engine-results"&gt;Aggregated Multi-Engine Results&lt;/h3&gt;
&lt;p&gt;Depending on your configuration, SearXNG queries multiple search engines simultaneously and combines the results. This provides several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Results that are heavily weighted on one engine might not appear prominently on others. Aggregation surfaces these differences.&lt;/li&gt;
&lt;li&gt;You get a more comprehensive view of information.&lt;/li&gt;
&lt;li&gt;If one engine has poor coverage for a topic, others may fill the gap.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The default configuration queries Google, Bing, DuckDuckGo, Qwant, Startpage, and many others. You can customise which engines to use through the settings interface.&lt;/p&gt;
&lt;h2 id="privacy-considerations"&gt;Privacy Considerations&lt;/h2&gt;
&lt;p&gt;While SearXNG prevents search engines from tracking your queries directly, there’s an important caveat: &lt;em&gt;your IP address is still visible to the search engines&lt;/em&gt; when SearXNG queries them on your behalf. This means search providers might still build a profile based on the searches coming from your server’s IP.&lt;/p&gt;
&lt;p&gt;To mitigate this, consider one of the following approaches:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Use a VPN container&lt;/strong&gt;: Run &lt;a href="https://github.com/qdm12/gluetun"&gt;Gluetun&lt;/a&gt; alongside SearXNG to route all search queries through a VPN. This masks your server’s IP address.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use a shared VPN exit node&lt;/strong&gt;: If you have access to a VPN service with shared IP addresses, your queries will blend in with traffic from other users.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This might be overkill for most home users. The benefits of no tracking cookies, personalised advertising, or search history are already a substantial improvement over using search engines directly. It is worth partially implementing this over not implementing this at all.&lt;/p&gt;
&lt;h2 id="supporting-the-project"&gt;Supporting the Project&lt;/h2&gt;
&lt;p&gt;SearXNG is maintained by a volunteer community. The project doesn&amp;rsquo;t appear to have a direct donation mechanism, but you can support it in other ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Contribute code&lt;/strong&gt;: The project is written primarily in Python and welcomes contributions on &lt;a href="https://github.com/searxng/searxng"&gt;GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Report bugs and suggest features&lt;/strong&gt;: Use the &lt;a href="https://github.com/searxng/searxng/issues"&gt;issue tracker&lt;/a&gt; to help improve the project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Help with translations&lt;/strong&gt;: SearXNG supports multiple languages, and translation contributions are always welcome.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Join the community&lt;/strong&gt;: The &lt;a href="https://matrix.to/#/#searxng:matrix.org"&gt;Matrix channel&lt;/a&gt; (#searxng:matrix.org) is active and welcoming to newcomers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Running a public instance (if you have the resources) also helps the broader community access privacy-respecting search.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;SearXNG provides a practical way to reclaim control over your search experience. The deployment is straightforward with Docker Compose, and the benefits are immediate: ad-free results, no tracking, and a free web search API.&lt;/p&gt;
&lt;p&gt;SearXNG is worth considering for those who are concerned about privacy or wish to avoid advertisements. Self-hosting offers the advantage of integrating with local LLMs for web search. The configuration above should get you running in under ten minutes.&lt;/p&gt;
&lt;p&gt;Have you set up SearXNG or another privacy-focused search solution? Share your experience in the comments.&lt;/p&gt;</content:encoded><category>self-hosting</category><category>docker</category><category>privacy</category><category>search-engine</category><category>large-language-models</category></item><item><title>It-Tools: Self Hosted Collection of Developer Tools and It Utilities</title><link>https://coderscompass.org/articles/it-tools-self-hosted-collection-of-developer-tools-and-it-utilities/</link><guid isPermaLink="true">https://coderscompass.org/articles/it-tools-self-hosted-collection-of-developer-tools-and-it-utilities/</guid><pubDate>Mon, 08 Dec 2025 10:13:15 +0000</pubDate><dc:creator>Subhomoy Haldar</dc:creator><description>Self-host IT-Tools, a collection of handy developer utilities like formatters and converters. Keep sensitive data private within your network.</description><content:encoded>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://it-tools.tech/"&gt;IT-Tools&lt;/a&gt; is a self-hosted collection of handy tools for IT usage. It includes converters, formatters, prettifiers, and many other utilities that developers and IT professionals use regularly. The project is created by &lt;a href="https://corentin.tech/"&gt;Corentin Thomasset&lt;/a&gt; and is available on &lt;a href="https://github.com/CorentinTh/it-tools"&gt;GitHub&lt;/a&gt; under the GPL-3.0 licence.&lt;/p&gt;
&lt;p&gt;We have an instinct to search for tools like YAML or JSON formatters, word counters, and very simple hosted web tools. The problem with them is that we don’t know what’s happening with potentially sensitive data we hand over to them. It’s not recommended to paste private, possibly confidential information onto publicly available websites where we don’t know who the owner is and what’s being done with the data. You will definitely get scowled at by your senior developer!&lt;/p&gt;
&lt;p&gt;By self-hosting this collection of tools and keeping them within your private network, you have better control over where your data is being copy-pasted. This project is also open source, so you can check if by some means your data is being uploaded to a third-party service. We&amp;rsquo;ve checked the IT-Tools repository and confirmed there&amp;rsquo;s no Plausible integration in the self-hosted version. See the &lt;a href="#analytics-on-the-public-instance"&gt;Analytics on the Public Instance&lt;/a&gt; section for details. It&amp;rsquo;s very unlikely, and it would erode all the trust in the project if something were found in the source code. Following such an event, there definitely would be forks of the project, and we would migrate to them as a community.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/get-started/get-docker/"&gt;Docker&lt;/a&gt; version 20.10 or later installed on your system. Newer versions of Docker include the Docker Compose plugin. Refer to the &lt;a href="#verification"&gt;Verification&lt;/a&gt; section to ensure you have everything set up properly.&lt;/li&gt;
&lt;li&gt;An optional &lt;a href="https://en.wikipedia.org/wiki/Reverse_proxy"&gt;reverse proxy&lt;/a&gt; if you want to expose it securely (e.g., &lt;a href="https://caddyserver.com/"&gt;Caddy&lt;/a&gt;, &lt;a href="https://traefik.io/"&gt;Traefik&lt;/a&gt;, or &lt;a href="https://nginx.org/"&gt;Nginx&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="verification"&gt;Verification&lt;/h2&gt;
&lt;p&gt;Before proceeding with the installation, verify that Docker is installed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-0-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-0-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker --version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You should see output similar to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-1-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-1-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;Docker version 29.0.1, build eedd9698e9
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Optionally, verify Docker Compose is installed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-2-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-2-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker compose version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Expected output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-3-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-3-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;Docker Compose version 2.40.3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;This is one of the simplest Docker deployments you can have. You don’t even need any volumes with this. It can be a stateless container.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The image is available from both Docker Hub (&lt;code&gt;corentinth/it-tools:latest&lt;/code&gt;) and GitHub Container Registry (&lt;code&gt;ghcr.io/corentinth/it-tools:latest&lt;/code&gt;). Both are official and maintained. We use the Docker Hub version for brevity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="using-docker-run"&gt;Using Docker Run&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-4-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker run -d &lt;span style="color:#8caaee"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-4-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span&gt; --name it-tools &lt;span style="color:#8caaee"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-4-3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span&gt; --restart unless-stopped &lt;span style="color:#8caaee"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-4-4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span&gt; -p 8080:80 &lt;span style="color:#8caaee"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-4-5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-4-5"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;span&gt; corentinth/it-tools:latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="using-docker-compose"&gt;Using Docker Compose&lt;/h3&gt;
&lt;p&gt;Sometimes you want a more permanent version control way of deploying your tools. We can achieve this using a Docker Compose file.&lt;/p&gt;
&lt;p&gt;Choose or create an appropriate directory. Something like &lt;code&gt;~/self-hosting/it-tools&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;docker-compose.yaml&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-5-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ca9ee6"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-5-2"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-2"&gt;2&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;it-tools&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-5-3"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-3"&gt;3&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;image&lt;/span&gt;: corentinth/it-tools:latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-5-4"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-4"&gt;4&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;container_name&lt;/span&gt;: it-tools
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-5-5"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-5"&gt;5&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;restart&lt;/span&gt;: unless-stopped
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-5-6"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-6"&gt;6&lt;/a&gt;&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ca9ee6"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-5-7"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-5-7"&gt;7&lt;/a&gt;&lt;/span&gt;&lt;span&gt; - “8080:80”
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then within that directory run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#c6d0f5;background-color:#303446;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#838ba7" id="hl-6-1"&gt;&lt;a style="outline:none;text-decoration:none;color:inherit" href="#hl-6-1"&gt;1&lt;/a&gt;&lt;/span&gt;&lt;span&gt;docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="test-the-deployment"&gt;Test the Deployment&lt;/h3&gt;
&lt;p&gt;Access the application at &lt;code&gt;localhost:8080&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="available-tools"&gt;Available Tools&lt;/h2&gt;
&lt;p&gt;As of December 2025, the latest version is &lt;a href="https://github.com/CorentinTh/it-tools/releases/tag/v2024.10.22-7ca5933"&gt;&lt;strong&gt;v2024.10.22-7ca5933&lt;/strong&gt;&lt;/a&gt;, released on 22 October 2024.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/it-tools/tool-search-shortcut.webp" alt="Tool search with keyboard shortcut" decoding="async"&gt;
&lt;em&gt;Quickly search for tools using the keyboard shortcut.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;IT-Tools includes over 100 utilities organised into convenient categories. Here’s the list of tools available by category:&lt;/p&gt;
&lt;h3 id="cryptographic-utilities"&gt;Cryptographic Utilities&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Token generator&lt;/li&gt;
&lt;li&gt;Hash text (MD5, SHA1, SHA256, SHA224, SHA512, SHA384, SHA3, RIPEMD160)&lt;/li&gt;
&lt;li&gt;Bcrypt&lt;/li&gt;
&lt;li&gt;UUIDs generator&lt;/li&gt;
&lt;li&gt;ULID generator&lt;/li&gt;
&lt;li&gt;Encrypt / decrypt text&lt;/li&gt;
&lt;li&gt;BIP39 passphrase generator&lt;/li&gt;
&lt;li&gt;HMAC generator&lt;/li&gt;
&lt;li&gt;RSA key pair generator&lt;/li&gt;
&lt;li&gt;Password strength analyser&lt;/li&gt;
&lt;li&gt;PDF signature checker&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/it-tools/random-token-generator.webp" alt="Random token generator interface" loading="lazy" decoding="async"&gt;
&lt;em&gt;Generate random tokens for various use cases.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="converters"&gt;Converters&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Date-time converter&lt;/li&gt;
&lt;li&gt;Integer base converter&lt;/li&gt;
&lt;li&gt;Roman numeral converter&lt;/li&gt;
&lt;li&gt;Base64 string encoder/decoder&lt;/li&gt;
&lt;li&gt;Base64 file converter&lt;/li&gt;
&lt;li&gt;Colour converter&lt;/li&gt;
&lt;li&gt;Case converter&lt;/li&gt;
&lt;li&gt;Text to NATO alphabet&lt;/li&gt;
&lt;li&gt;Text to ASCII binary&lt;/li&gt;
&lt;li&gt;Text to Unicode&lt;/li&gt;
&lt;li&gt;YAML to JSON converter&lt;/li&gt;
&lt;li&gt;YAML to TOML&lt;/li&gt;
&lt;li&gt;JSON to YAML converter&lt;/li&gt;
&lt;li&gt;JSON to TOML&lt;/li&gt;
&lt;li&gt;List converter&lt;/li&gt;
&lt;li&gt;TOML to JSON&lt;/li&gt;
&lt;li&gt;TOML to YAML&lt;/li&gt;
&lt;li&gt;XML to JSON&lt;/li&gt;
&lt;li&gt;JSON to XML&lt;/li&gt;
&lt;li&gt;Markdown to HTML&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/it-tools/base64-encoder-decoder.webp" alt="Base64 encoder and decoder tool" loading="lazy" decoding="async"&gt;
&lt;em&gt;Encode and decode Base64 strings easily.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="tools-for-the-web"&gt;Tools for the Web&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Encode/decode URL-formatted strings&lt;/li&gt;
&lt;li&gt;Escape HTML entities&lt;/li&gt;
&lt;li&gt;URL parser&lt;/li&gt;
&lt;li&gt;Device information&lt;/li&gt;
&lt;li&gt;Basic auth generator&lt;/li&gt;
&lt;li&gt;Open graph meta generator&lt;/li&gt;
&lt;li&gt;OTP code generator&lt;/li&gt;
&lt;li&gt;MIME types&lt;/li&gt;
&lt;li&gt;JWT parser&lt;/li&gt;
&lt;li&gt;Keycode info&lt;/li&gt;
&lt;li&gt;Slugify string&lt;/li&gt;
&lt;li&gt;HTML WYSIWYG editor&lt;/li&gt;
&lt;li&gt;User-agent parser&lt;/li&gt;
&lt;li&gt;HTTP status codes&lt;/li&gt;
&lt;li&gt;JSON diff&lt;/li&gt;
&lt;li&gt;Outlook Safelink decoder&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="images-and-videos"&gt;Images and Videos&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;QR Code generator&lt;/li&gt;
&lt;li&gt;Wi-Fi QR code generator&lt;/li&gt;
&lt;li&gt;SVG placeholder generator&lt;/li&gt;
&lt;li&gt;Camera recorder&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/it-tools/qr-code-generator.webp" alt="QR code generator interface" loading="lazy" decoding="async"&gt;
&lt;em&gt;Generate QR codes for URLs, text, and more.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="development"&gt;Development&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Git cheat sheet&lt;/li&gt;
&lt;li&gt;Random port generator&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crontab&lt;/code&gt; generator&lt;/li&gt;
&lt;li&gt;JSON prettify and format&lt;/li&gt;
&lt;li&gt;JSON minify&lt;/li&gt;
&lt;li&gt;JSON to CSV&lt;/li&gt;
&lt;li&gt;SQL prettify and formatter&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chmod&lt;/code&gt; calculator&lt;/li&gt;
&lt;li&gt;Docker run to Docker compose converter&lt;/li&gt;
&lt;li&gt;XML formatter&lt;/li&gt;
&lt;li&gt;YAML prettify and format&lt;/li&gt;
&lt;li&gt;Email normaliser&lt;/li&gt;
&lt;li&gt;Regex Tester&lt;/li&gt;
&lt;li&gt;Regex cheat sheet&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/it-tools/docker-run-to-compose.webp" alt="Docker run to Docker compose converter" loading="lazy" decoding="async"&gt;
&lt;em&gt;Convert Docker run commands to Docker compose files.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="network"&gt;Network&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;IPv4 subnet calculator&lt;/li&gt;
&lt;li&gt;IPv4 address converter&lt;/li&gt;
&lt;li&gt;IPv4 range expander&lt;/li&gt;
&lt;li&gt;MAC address lookup&lt;/li&gt;
&lt;li&gt;MAC address generator&lt;/li&gt;
&lt;li&gt;IPv6 ULA generator&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="mathematics"&gt;Mathematics&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Math evaluator&lt;/li&gt;
&lt;li&gt;ETA calculator&lt;/li&gt;
&lt;li&gt;Percentage calculator&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="measurement"&gt;Measurement&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Chronometer&lt;/li&gt;
&lt;li&gt;Temperature converter&lt;/li&gt;
&lt;li&gt;Benchmark builder&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="text-utilities"&gt;Text Utilities&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Lorem ipsum generator&lt;/li&gt;
&lt;li&gt;Text statistics&lt;/li&gt;
&lt;li&gt;Emoji picker&lt;/li&gt;
&lt;li&gt;String obfuscator&lt;/li&gt;
&lt;li&gt;Text diff&lt;/li&gt;
&lt;li&gt;Numeronym generator&lt;/li&gt;
&lt;li&gt;ASCII Art Text Generator&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://coderscompass.org/images/articles/it-tools/lorem-ipsum-generator.webp" alt="Lorem ipsum generator tool" loading="lazy" decoding="async"&gt;
&lt;em&gt;Generate placeholder text for your projects.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="data-processing-tools"&gt;Data Processing Tools&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Phone parser and formatter&lt;/li&gt;
&lt;li&gt;IBAN validator and parser&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics-on-the-public-instance"&gt;Analytics on the Public Instance&lt;/h2&gt;
&lt;p&gt;The publicly hosted version of IT-Tools uses Plausible as an analytics platform, as evidenced by the &lt;a href="https://github.com/CorentinTh/it-tools/blob/0de73e8971b4c977086d246b112f218ed3dcc3f8/src/plugins/plausible.plugin.ts"&gt;plausible.plugin.ts file&lt;/a&gt; in the source code. However, when you self-host it using the Docker image, there is no analytics tracking.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;a href="https://github.com/CorentinTh/it-tools/blob/0de73e8971b4c977086d246b112f218ed3dcc3f8/Dockerfile"&gt;Dockerfile&lt;/a&gt; does not include any environment variables or configuration for Plausible&lt;/li&gt;
&lt;li&gt;The Plausible plugin requires explicit configuration to function, which is absent in the self-hosted deployment.&lt;/li&gt;
&lt;li&gt;All tools are Vue.js components that process data locally within the browser.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Other than this, there’s no evidence of data being sent to any third parties. All tools are Vue.js components that process data within themselves. Some of them use local storage, but none of them do any data transfers. There are no traces of any HTTP usage.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Overall, IT-Tools is a nifty collection of handy tools all in one place. It’s highly recommended that you try it. The deployment is straightforward, and having these tools self-hosted (ideally within your local network) means you can use them without worrying about data privacy.&lt;/p&gt;
&lt;p&gt;If you derive a lot of value from this little tool, we recommend &lt;a href="https://github.com/sponsors/CorentinTh"&gt;sponsoring Corentin on GitHub&lt;/a&gt; so that we can keep this wonderful project going! You can also &lt;a href="https://buymeacoffee.com/cthmsst"&gt;buy Corentin a coffee&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Know any other such tools worth self-hosting? Let us know in the comments!&lt;/p&gt;</content:encoded><category>self-hosting</category><category>docker</category><category>utilities</category><category>developer-tools</category><category>privacy</category></item><item><title>Comparing TODIM and CPT-TODIM for Social Sustainability Assessment in G7 Countries</title><link>https://coderscompass.org/articles/todim-cpt-todim-social-sustainability-g7/</link><guid isPermaLink="true">https://coderscompass.org/articles/todim-cpt-todim-social-sustainability-g7/</guid><pubDate>Tue, 09 Sep 2025 00:00:00 +0000</pubDate><dc:creator>Vaishnudebi Dutta</dc:creator><dc:creator>Subhomoy Haldar</dc:creator><description>We compare decision-making algorithms to rank G7 countries on social sustainability. CPT-TODIM proves most stable out of TODIM and CoCoSo.</description><content:encoded>&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;This paper came from a question we asked: if you want to rank countries on how “socially sustainable” they are, which algorithm should you use? Many multi-criteria decision-making (MCDM) algorithms exist, yet most research focuses on a single one. We wanted to dig deeper.&lt;/p&gt;
&lt;p&gt;We focused on TODIM and its enhanced variant CPT-TODIM because they’re based on &lt;a href="https://en.wikipedia.org/wiki/Prospect_theory"&gt;prospect theory&lt;/a&gt;. People are more affected by losses than by gains, as the Nobel Prize-winning idea suggests. This makes them well-suited for decisions where subjective judgement matters.&lt;/p&gt;
&lt;h2 id="the-problem-in-plain-english"&gt;The Problem in Plain English&lt;/h2&gt;
&lt;p&gt;Suppose you want to rank the G7 countries (Canada, France, Germany, Italy, Japan, UK, USA) on social sustainability. You have 14 criteria: average wage, employment rate, income inequality, women in politics, education levels, and so on. How do you combine all these numbers into a single ranking?&lt;/p&gt;
&lt;p&gt;Most algorithms work by computing a “distance” to some ideal solution. The country closest to the ideal wins. The problem is that by compressing all that multi-dimensional data into a single distance score, you lose nuance. Two very different countries might end up with similar distances, making the ranking unstable.&lt;/p&gt;
&lt;p&gt;TODIM takes a different approach. Instead of evaluating countries based on their distance from a theoretical ideal, this method involves a criterion-by-criterion comparison of every country. For each pair, it asks: “Is Country A better or worse than Country B on this criterion? By how much?” Then it weighs losses more heavily than gains (because that’s how humans think). The result is a more stable ranking that doesn’t flip the alternatives around when you add or remove options.&lt;/p&gt;
&lt;p&gt;CPT-TODIM extends this by adding parameters that model risk attitudes more precisely. It transforms the criterion weights using cumulative prospect theory, giving decision-makers finer control over how gains and losses are perceived.&lt;/p&gt;
&lt;h2 id="measuring-rank-reversals"&gt;Measuring Rank Reversals&lt;/h2&gt;
&lt;p&gt;We assessed algorithm stability through &lt;a href="https://en.wikipedia.org/wiki/Rank_reversals_in_decision-making"&gt;rank reversal&lt;/a&gt; analysis. Rank reversal occurs when removing one option changes the ordering of the remaining choices. We tested each algorithm by removing one G7 country at a time and checking if the remaining rankings shifted:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Rank Reversals (out of 7 removals)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CoCoSo&lt;/td&gt;
&lt;td&gt;4 (FR, IT, JP, USA removals)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TODIM&lt;/td&gt;
&lt;td&gt;2 (CA, DE removals)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPT-TODIM&lt;/td&gt;
&lt;td&gt;1 (DE removal only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;CoCoSo proved unreliable, with more than half of removals causing rank reversals. Both TODIM variants displayed minimal reversals, with CPT-TODIM being the most stable.&lt;/p&gt;
&lt;h2 id="abstract"&gt;Abstract&lt;/h2&gt;
&lt;p&gt;Social sustainability objectives within the framework of sustainable development goals (SDGs) are critical aspects of balanced societies. Governments must constantly assess their performance to accomplish social sustainability goals. This paper evaluates the performance of seven industrialised nations: members of the Group of Seven (G7), in a streamlined manner. The rank sum weighting approach was used to quantify the subjectivity of experts’ judgements. In order to assess social sustainability performance for G7 countries, TODIM (TOmada de Decisão Interativa e Multicritério) and Cumulative Prospect Theory TODIM (CPT-TODIM) are implemented to compare the social sustainability performance of the G7 nations. Furthermore, sensitivity analysis is performed over 1000 different weight permutations of the criteria and alternatives from the original roster are removed to evaluate and contrast the two Multi-Criteria Decision-Making (MCDM) algorithms used to gain a deeper understanding of the strengths and limitations of each approach such as rank reversal problem. This analysis has also led to the conclusion of CPT-TODIM outranking both TODIM and CoCoSo algorithms. Analysis of 2020 OECD iLibrary data using both algorithms revealed that France emerged as the most socially sustainable country among the G7 nations.&lt;/p&gt;
&lt;h2 id="publication-details"&gt;Publication Details&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Journal:&lt;/strong&gt; International Journal of Information Technology &amp;amp; Decision Making&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Published:&lt;/strong&gt; 9 September 2025&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DOI:&lt;/strong&gt; &lt;a href="https://www.worldscientific.com/doi/10.1142/S021962202550097X"&gt;10.1142/S021962202550097X&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publisher:&lt;/strong&gt; World Scientific&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISSN:&lt;/strong&gt; 0219-6220 (Print), 1793-6845 (Online)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open Access:&lt;/strong&gt; &lt;a href="https://research-information.bris.ac.uk/en/publications/performance-comparison-of-todim-and-cpt-todim-in-social-sustainab/"&gt;Accepted Author Manuscript (University of Bristol)&lt;/a&gt; available under &lt;a href="https://creativecommons.org/licenses/by/4.0/"&gt;CC BY 4.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="authors"&gt;Authors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://coderscompass.org/authors/vaishnudebi-dutta/"&gt;Vaishnudebi Dutta&lt;/a&gt; (Corresponding Author, University of Bristol)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://coderscompass.org/authors/subhomoy-haldar/"&gt;Subhomoy Haldar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="keywords"&gt;Keywords&lt;/h2&gt;
&lt;p&gt;Social sustainability, multi-criteria decision-making, prospect theory, TODIM, CPT-TODIM, sensitivity analysis&lt;/p&gt;
&lt;h2 id="code-and-data"&gt;Code and Data&lt;/h2&gt;
&lt;p&gt;The Python code for running the comparative and sensitivity analysis is available on GitHub: &lt;a href="https://github.com/Coders-Compass/social-sustainability"&gt;Coders-Compass/social-sustainability&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The study uses 2020 data from the &lt;a href="https://www.oecd.org/en/publications.html"&gt;OECD iLibrary&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="access-the-publication"&gt;Access the Publication&lt;/h2&gt;
&lt;p&gt;The publisher version is available through the &lt;a href="https://www.worldscientific.com/doi/10.1142/S021962202550097X"&gt;DOI link&lt;/a&gt; (closed access). In the interest of open access, the &lt;strong&gt;accepted author manuscript&lt;/strong&gt; is freely available from the &lt;a href="https://research-information.bris.ac.uk/en/publications/performance-comparison-of-todim-and-cpt-todim-in-social-sustainab/"&gt;University of Bristol research repository&lt;/a&gt; under a &lt;a href="https://creativecommons.org/licenses/by/4.0/"&gt;Creative Commons Attribution 4.0&lt;/a&gt; license, made open access under the university&amp;rsquo;s Scholarly Works Policy.&lt;/p&gt;
&lt;p&gt;The paper contains the complete mathematical derivations, sensitivity analysis heat maps, and policy implications for each G7 country.&lt;/p&gt;</content:encoded><category>research</category><category>publications</category><category>academic</category><category>mcdm</category><category>todim</category><category>decision-making</category><category>sustainability</category><category>prospect-theory</category></item><item><title>Probability of Real Roots in a Quadratic Equation with Uniform(α,β) Coefficients</title><link>https://coderscompass.org/articles/probability-real-roots-quadratic-uniform-alpha-beta/</link><guid isPermaLink="true">https://coderscompass.org/articles/probability-real-roots-quadratic-uniform-alpha-beta/</guid><pubDate>Wed, 29 Mar 2023 00:00:00 +0000</pubDate><dc:creator>Subhomoy Haldar</dc:creator><description>We derive a general formula for the probability of real roots when quadratic coefficients are U(α,β), extending our symmetric case result.</description><content:encoded>&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;This paper is a direct follow-up to our &lt;a href="https://coderscompass.org/articles/probability-real-roots-quadratic-iid-uniform-theta/"&gt;earlier work on the symmetric case&lt;/a&gt;, where we found that the probability of obtaining real roots of a quadratic equation is approximately 62.7% when coefficients are drawn from $U(-\theta, \theta)$.&lt;/p&gt;
&lt;p&gt;The question that follows naturally is what happens when we remove the symmetry constraint?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What if the coefficients come from $U(\alpha, \beta)$ for any real numbers $\alpha$ and $\beta$?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The generalisation was more challenging than expected. We could apply simplifications for the symmetric case. For this instance, we had to employ a more powerful approach of obtaining the value - vector calculus. Our co-author &lt;a href="https://www.linkedin.com/in/mkchaudhary/"&gt;Mukesh Chaudhary&lt;/a&gt; provided the insight necessary to proceed.&lt;/p&gt;
&lt;h2 id="the-problem-in-plain-english"&gt;The Problem in Plain English&lt;/h2&gt;
&lt;p&gt;Consider the quadratic equation $Ax^2 + Bx + C = 0$. It has real roots when $B^2 \geq 4AC$. We want to find the probability of this happening when $A$, $B$, and $C$ are independently drawn from a uniform distribution $U(\alpha, \beta)$.&lt;/p&gt;
&lt;p&gt;The geometric intuition follows: when we have three independent real values, we’re essentially working in a 3D space where each point $(A, B, C)$ represents one set of coefficients. Out of this entire cube, only certain sub-regions satisfy the discriminant condition $B^2 \geq 4AC$.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The probability we&amp;rsquo;re after is the volume of this &amp;ldquo;solution region&amp;rdquo; divided by the total volume.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The shape of this solution region is rather intricate. In the symmetric case, we could work around the complexity. But in the general case, we needed to compute these volumes properly using vector calculus.&lt;/p&gt;
&lt;h2 id="the-key-finding"&gt;The Key Finding&lt;/h2&gt;
&lt;p&gt;Unlike the symmetric case, which gives a single probability (62.7%), the general case produces different probabilities depending on the relationship between $\alpha$ and $\beta$. We derived the following algorithm that handles all cases:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Algorithm to calculate the probability of obtaining a real root when the coefficients are sampled from the continuous distribution $U(\alpha, \beta)$, where $\alpha, \beta \in \mathbb{R}$
:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Calculate the scaling factor $\theta = \dfrac{\text{signum}(\alpha) \cdot \text{signum}(\beta)}{\max(|\alpha|, |\beta|)}$&lt;/li&gt;
&lt;li&gt;Find the ratio $f = \min(|\alpha|, |\beta|) \cdot \theta$, and its absolute value $r = |f|$&lt;/li&gt;
&lt;li&gt;Calculate total volume of the cube as $V = (1 - f)^3$&lt;/li&gt;
&lt;li&gt;Pre-compute $V_0 = \dfrac{5}{36} + \dfrac{\log 2}{6} \approx 0.2544$
It is also recommended to compute: $r^2$, $r^3$, $r^{\frac{3}{2}}$, $\dfrac{\log r}{6}$&lt;/li&gt;
&lt;li&gt;If $f = 0$, return $V_0 \approx 0.2544$&lt;/li&gt;
&lt;li&gt;If $f \in \left[\dfrac{1}{2}, 1\right]$, return $0$&lt;/li&gt;
&lt;li&gt;If $f \in \left[\dfrac{1}{4}, \dfrac{1}{2}\right)$, return $\left(-V_0 - \dfrac{\log r}{6} + r^2 - \dfrac{8}{9}r^3\right) \div V$&lt;/li&gt;
&lt;li&gt;If $f \in \left(0, \dfrac{1}{4}\right)$, return $\left(V_0 - 2r + \dfrac{16}{9}r^{\frac{3}{2}} + r^2 - \dfrac{8}{9}r^3\right) \div V$&lt;/li&gt;
&lt;li&gt;If $f \in \left[-\dfrac{1}{2}, 0\right)$, return $\left(V_0 + 2r + 3r^2 + r^3\left(2V_0 - \dfrac{\log r}{6} - \dfrac{8}{9}\right)\right) \div V$&lt;/li&gt;
&lt;li&gt;If $f \in \left[-1, -\dfrac{1}{2}\right)$, return $\left(2(V_0 + r + r^2) + \dfrac{\log r}{6} + r^3\left(2V_0 - \dfrac{\log r}{6}\right)\right) \div V$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The constant $V_0 = \dfrac{5}{36} + \dfrac{\log 2}{6}$ appears throughout the formula and represents the base probability in certain limiting cases.&lt;/p&gt;
&lt;h2 id="abstract"&gt;Abstract&lt;/h2&gt;
&lt;p&gt;The roots of a quadratic equation $Ax^2 + Bx + C = 0$ are real if the discriminant $B^2 - 4AC$ is non-negative. In this paper, the coefficients are taken as independently and identically distributed $U(\alpha, \beta)$, where $\alpha, \beta \in \mathbb{R}$. The exact probability of obtaining real roots is derived through simplification by considering the ratio of the endpoints. Vector calculus is used to derive the final formula, which is experimentally verified using Monte Carlo simulation.&lt;/p&gt;
&lt;h2 id="publication-details"&gt;Publication Details&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Journal:&lt;/strong&gt; Journal of the Indian Society for Probability and Statistics&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Volume:&lt;/strong&gt; 24, Pages 135–149&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Published:&lt;/strong&gt; March 2023&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DOI:&lt;/strong&gt; &lt;a href="https://doi.org/10.1007/s41096-023-00149-6"&gt;10.1007/s41096-023-00149-6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publisher:&lt;/strong&gt; Springer India&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISSN:&lt;/strong&gt; 2364-9569 (Online)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="authors"&gt;Authors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://coderscompass.org/authors/subhomoy-haldar/"&gt;Subhomoy Haldar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orcid.org/0000-0003-3530-9027"&gt;Dr. Soubhik Chakraborty&lt;/a&gt; (Supervisor)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/mkchaudhary/"&gt;Mukesh Chaudhary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="keywords"&gt;Keywords&lt;/h2&gt;
&lt;p&gt;Quadratic equation, continuous uniform distribution, probability, Monte Carlo simulation, vector calculus&lt;/p&gt;
&lt;h2 id="related-work"&gt;Related Work&lt;/h2&gt;
&lt;p&gt;This paper extends our &lt;a href="https://coderscompass.org/articles/probability-real-roots-quadratic-iid-uniform-theta/"&gt;earlier result for the symmetric case&lt;/a&gt;, where coefficients were drawn from $U(-\theta, \theta)$. That work established the foundational approach and the 62.7% probability for the symmetric distribution.&lt;/p&gt;
&lt;h2 id="access-the-publication"&gt;Access the Publication&lt;/h2&gt;
&lt;p&gt;The full paper is available through the DOI link above. It contains the complete mathematical derivations, proofs, and Monte Carlo simulation results that verify the formula. We also show a render of the acceptable volume within the unit cube that represents the region where roots are real.&lt;/p&gt;</content:encoded><category>research</category><category>publications</category><category>academic</category><category>probability</category><category>mathematics</category><category>statistics</category><category>vector-calculus</category></item><item><title>Implications of Agricultural Practices on Vegetation and Terrestrial Invertebrates in Riparian Zones</title><link>https://coderscompass.org/articles/agricultural-practices-riparian-vegetation-invertebrates-nonlinear-model/</link><guid isPermaLink="true">https://coderscompass.org/articles/agricultural-practices-riparian-vegetation-invertebrates-nonlinear-model/</guid><pubDate>Fri, 30 Sep 2022 00:00:00 +0000</pubDate><dc:creator>Vaishnudebi Dutta</dc:creator><description>A nonlinear mathematical model shows how unchecked agricultural expansion into riparian zones threatens vegetation and invertebrate populations. Stability analysis reveals the conditions for coexistence.</description><content:encoded>&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;Riparian zones are the strips of land that run alongside rivers, streams, and wetlands. They act as transition areas between water and land, supporting a remarkable range of life. The vegetation growing in these zones filters sediment, regulates nutrients, stabilises stream banks, and moderates water temperature. Invertebrates (insects, spiders, beetles, and similar creatures) living in this zone depend on this vegetation for food, shelter, hunting grounds, and breeding sites.&lt;/p&gt;
&lt;p&gt;Farmers increasingly use riparian land for agriculture. The rising population drives demand for more cultivable area, and riparian zones, with their fertile, well-watered soil, become tempting targets. The trouble is that clearing vegetation for farming removes the habitat that invertebrates rely on, setting off a chain of ecological consequences.&lt;/p&gt;
&lt;p&gt;Field studies have documented these effects at various locations around the world, but mathematical models capturing the three-way interaction between riparian vegetation, terrestrial invertebrates, and agricultural production have been rare. This paper fills that gap by proposing a nonlinear model built from differential equations, then analysing its behaviour to identify the conditions under which all three can coexist.&lt;/p&gt;
&lt;h2 id="the-problem-in-plain-english"&gt;The Problem in Plain English&lt;/h2&gt;
&lt;p&gt;Picture a stretch of riverbank. Trees and shrubs grow along it, providing cover for spiders, beetles, and other small creatures. These invertebrates do useful things: some eat crop pests, others help pollinate nearby fields, and their activity contributes to healthy soil. The vegetation also acts as a buffer, trapping excess nitrogen and phosphorus before it reaches the water.&lt;/p&gt;
&lt;p&gt;Now imagine a farmer clears part of that riverbank to plant crops. The vegetation shrinks, and so does the habitat for invertebrates. Fewer invertebrates mean fewer natural pest controllers and pollinators, which can actually hurt the farm’s own output in the long run.&lt;/p&gt;
&lt;p&gt;The paper captures this situation with three variables that change over time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$V(t)$&lt;/strong&gt; - the amount of riparian vegetation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$I(t)$&lt;/strong&gt; - the population of terrestrial invertebrates&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$A(t)$&lt;/strong&gt; - the agricultural production&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each variable is governed by a differential equation describing how it grows, shrinks, and interacts with the others. Vegetation grows logistically (it can only get so big given finite land and water) but gets reduced by both farming and invertebrate consumption. Invertebrates grow from vegetation but face internal competition and harm from agricultural activity. Agriculture also grows logistically but gets a boost from invertebrates (through pest control and pollination).&lt;/p&gt;
&lt;h2 id="key-findings"&gt;Key Findings&lt;/h2&gt;
&lt;p&gt;The mathematical analysis identified six possible long-term states the system could settle into:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Equilibrium&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$E_0$&lt;/td&gt;
&lt;td&gt;Everything collapses: no vegetation, no invertebrates, no agriculture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$E_1$&lt;/td&gt;
&lt;td&gt;Only agriculture survives; vegetation and invertebrates are gone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$E_2$&lt;/td&gt;
&lt;td&gt;Only vegetation survives; no invertebrates or agriculture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$E_3$&lt;/td&gt;
&lt;td&gt;Vegetation and agriculture coexist, but invertebrates have vanished&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$E_4$&lt;/td&gt;
&lt;td&gt;Vegetation and invertebrates coexist, but there is no agriculture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$E_5$&lt;/td&gt;
&lt;td&gt;All three coexist: vegetation, invertebrates, and agriculture together&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The most important result concerns $E_5$, the coexistence state. The paper shows it exists and is stable when:&lt;/p&gt;
&lt;p&gt;$$\theta\beta &amp;gt; \left(1 + \frac{\alpha}{r}\right)\delta$$&lt;/p&gt;
&lt;p&gt;In words: the benefit that invertebrates draw from vegetation ($\theta\beta$) must outweigh the damage that agriculture inflicts, scaled by how much farming erodes vegetation’s capacity to regrow. If agriculture encroaches too aggressively, this condition breaks, and the system collapses to $E_3$ - a system with farms and some vegetation, but no invertebrates.&lt;/p&gt;
&lt;p&gt;The sensitivity analysis reinforces this picture. The two most influential parameters are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$r$&lt;/strong&gt; (vegetation’s intrinsic growth rate) - higher values stabilise the system and help all three components reach equilibrium faster&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$\gamma$&lt;/strong&gt; (competition among invertebrates) - stronger competition reduces invertebrate numbers, which hurts agricultural output because fewer invertebrates mean less natural pest control&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When the depletion rate $\alpha$ (how fast farming destroys vegetation) is too high, the system destabilises. The simulations show growing oscillations and eventual collapse.&lt;/p&gt;
&lt;h2 id="what-this-means"&gt;What This Means&lt;/h2&gt;
&lt;p&gt;The mathematics formalises something ecologists have long suspected: riparian buffers - the uncultivated, vegetated strips next to waterways - are worth protecting. They sustain invertebrate populations that benefit farming through pollination, pest suppression, decomposition, and water quality regulation. Pushing agriculture into these zones might yield short-term gains, but the model shows it risks destabilising the entire system.&lt;/p&gt;
&lt;h2 id="abstract"&gt;Abstract&lt;/h2&gt;
&lt;p&gt;Uncontrolled land-use owing to agricultural practices has not only resulted in depletion of vegetation, but has also worsened the habitat of terrestrial invertebrates living in riparian zones. With these dynamic interactions in mind, a nonlinear model is developed using a set of differential equations, including riparian vegetation, terrestrial invertebrates, and agricultural production as system variables. The model is based on the notion that terrestrial invertebrates totally depend on riparian vegetation for their survival and utilization of riparian zones for agricultural purposes not only cause the loss of riparian vegetation, but of terrestrial invertebrates as well. The generated differential-equation system is examined for equilibrium solutions, their existences and stabilities. The mathematical analysis demonstrates the conditions under which agricultural production, riparian vegetation, and terrestrial invertebrates can coexist and create a stable system. These intuitive conclusions are supported by quantitative results using numerical simulation and differential sensitivity analysis. The qualitative as well as quantitative findings suggest that excessive utilization of forested riparian land for agricultural practices may cause destabilization of the system and therefore, they should be put under check in riparian zones.&lt;/p&gt;
&lt;h2 id="publication-details"&gt;Publication Details&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Journal:&lt;/strong&gt; International Journal of Applied and Computational Mathematics&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Volume:&lt;/strong&gt; 8, Article 268&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Published:&lt;/strong&gt; 30 September 2022&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DOI:&lt;/strong&gt; &lt;a href="https://doi.org/10.1007/s40819-022-01471-6"&gt;10.1007/s40819-022-01471-6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publisher:&lt;/strong&gt; Springer Nature&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISSN:&lt;/strong&gt; 2349-5103 (Print), 2199-5796 (Online)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="authors"&gt;Authors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://coderscompass.org/authors/vaishnudebi-dutta/"&gt;Vaishnudebi Dutta&lt;/a&gt; (Department of Mathematics, Birla Institute of Technology, Mesra, Ranchi)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orcid.org/0000-0003-0882-2669"&gt;Abhinav Tandon&lt;/a&gt; (Corresponding Author, Department of Mathematics, Birla Institute of Technology, Mesra, Ranchi)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="keywords"&gt;Keywords&lt;/h2&gt;
&lt;p&gt;Riparian vegetation, Agriculture, Mathematical model, Differential equation, Stability analysis, Sensitivity analysis&lt;/p&gt;
&lt;h2 id="access-the-publication"&gt;Access the Publication&lt;/h2&gt;
&lt;p&gt;The paper is available through the &lt;a href="https://doi.org/10.1007/s40819-022-01471-6"&gt;DOI link&lt;/a&gt; on Springer. It contains the complete mathematical derivations, proofs, phase portraits, time-series graphs, and sensitivity analysis bar charts.&lt;/p&gt;</content:encoded><category>research</category><category>publications</category><category>academic</category><category>ecology</category><category>mathematical-modelling</category><category>differential-equations</category><category>mathematics</category></item><item><title>Probability of Real Roots in a Quadratic Equation with Uniform(-θ,θ) Coefficients</title><link>https://coderscompass.org/articles/probability-real-roots-quadratic-iid-uniform-theta/</link><guid isPermaLink="true">https://coderscompass.org/articles/probability-real-roots-quadratic-iid-uniform-theta/</guid><pubDate>Fri, 12 Mar 2021 00:00:00 +0000</pubDate><dc:creator>Subhomoy Haldar</dc:creator><description>What's the chance a quadratic has real roots when coefficients are random? We find it's 62.7% using probability theory and Monte Carlo simulation.</description><content:encoded>&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;I wrote this paper towards the end of my Integrated Master of Science (IMSc) in Mathematics and Computing at Birla Institute of Technology, Mesra. Working under the guidance of &lt;a href="https://orcid.org/0000-0003-3530-9027"&gt;Dr. Soubhik Chakraborty&lt;/a&gt;, I came across an interesting problem that had seen some discussion in the early 20th century but had been largely forgotten since.&lt;/p&gt;
&lt;p&gt;The question is deceptively simple:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you pick random numbers for the coefficients of a quadratic equation, what’s the chance that it has real roots?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This turned out to be a wonderful exercise in probability theory. Beyond the mathematics itself, the goal was to show that there are still unexplored corners in well-trodden areas of mathematics waiting to be revisited.&lt;/p&gt;
&lt;h2 id="the-problem-in-plain-english"&gt;The Problem in Plain English&lt;/h2&gt;
&lt;p&gt;Consider the quadratic equation $Ax^2 + Bx + C = 0$. We know from school that it has real roots when the discriminant $B^2 - 4AC$ is non-negative. It takes an interesting angle when we ask: what if $A$, $B$, and $C$ are random numbers drawn independently from the same uniform distribution?&lt;/p&gt;
&lt;p&gt;We looked specifically at the symmetric case where each coefficient comes from $U(-\theta, \theta)$, meaning any value between $-\theta$ and $+\theta$ is equally likely.&lt;/p&gt;
&lt;h2 id="the-key-finding"&gt;The Key Finding&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The probability of obtaining real roots is approximately 62.7%.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This result comes from analysis of the probability distribution of $B^2$ and the conditions under which $B^2 \geq 4AC$. The calculation simplifies considerably when $AC \leq 0$ (which happens half the time), because then the discriminant is automatically non-negative.&lt;/p&gt;
&lt;p&gt;We verified this theoretical result experimentally through Monte Carlo simulation, running thousands of random trials to confirm the calculation.&lt;/p&gt;
&lt;h2 id="abstract"&gt;Abstract&lt;/h2&gt;
&lt;p&gt;In this paper, we seek to find out the probability of obtaining real roots of a quadratic equation $Ax^2 + Bx + C = 0$, with $A \neq 0$, when the coefficients are independent, identically distributed uniform variates. The exact value of the roots can be obtained from the coefficients and the discriminant indicates if the roots are real or imaginary. Here, we consider the uniform distribution $U(-\theta, \theta)$ and find the probability of obtaining a real root to be 62.7%. This is done through simplification of the problem, analysis of the probability distribution of $B^2$ for both $U(-1, 1)$ and $U(0, 1)$, and final evaluation using conditional probability. Calculations are simplified by the fact that $B^2 \geq 4AC$ is always true when $AC \leq 0$. We leverage on the fact that the probability of obtaining real roots when coefficients are sampled from $U(0, \theta)$ is 25.4%. We verify the result experimentally through Monte Carlo simulation and present the desired supporting data accordingly.&lt;/p&gt;
&lt;h2 id="publication-details"&gt;Publication Details&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Journal:&lt;/strong&gt; Journal of Applied Mathematics and Computation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Volume:&lt;/strong&gt; 5, Issue 1, Pages 48-55&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Published:&lt;/strong&gt; 12 March 2021&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DOI:&lt;/strong&gt; &lt;a href="https://doi.org/10.26855/jamc.2021.03.006"&gt;10.26855/jamc.2021.03.006&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publisher:&lt;/strong&gt; Hill Publishing Group Inc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISSN:&lt;/strong&gt; 2576-0653 (Online)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="authors"&gt;Authors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://coderscompass.org/authors/subhomoy-haldar/"&gt;Subhomoy Haldar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orcid.org/0000-0003-3530-9027"&gt;Dr. Soubhik Chakraborty&lt;/a&gt; (Supervisor)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="keywords"&gt;Keywords&lt;/h2&gt;
&lt;p&gt;Quadratic equation, continuous uniform distribution, probability, Monte Carlo simulation&lt;/p&gt;
&lt;h2 id="follow-up-work"&gt;Follow-up Work&lt;/h2&gt;
&lt;p&gt;This paper laid the groundwork for a more general result. In a &lt;a href="https://coderscompass.org/articles/probability-real-roots-quadratic-uniform-alpha-beta/"&gt;subsequent paper&lt;/a&gt;, we extended the analysis to the case where coefficients come from $U(\alpha, \beta)$ for any real numbers $\alpha$ and $\beta$, removing the symmetry constraint. That work was published in the Journal of the Indian Society for Probability and Statistics in 2023.&lt;/p&gt;
&lt;h2 id="access-the-publication"&gt;Access the Publication&lt;/h2&gt;
&lt;p&gt;The full paper is available as open access through the DOI link above. It contains the complete mathematical derivations, proofs, and simulation harness.&lt;/p&gt;</content:encoded><category>research</category><category>publications</category><category>academic</category><category>probability</category><category>mathematics</category><category>statistics</category></item></channel></rss>