<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Self-Hosted on nuteo.info</title><link>https://nuteo.info/en/tags/self-hosted/</link><description>Recent content in Self-Hosted on nuteo.info</description><generator>Hugo</generator><language>en</language><copyright>© 2026 Nuteo</copyright><lastBuildDate>Mon, 03 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://nuteo.info/en/tags/self-hosted/index.xml" rel="self" type="application/rss+xml"/><item><title>Healthchecks.io: cron monitoring ที่ใช้ได้จริง</title><link>https://nuteo.info/en/tools/healthchecks/</link><pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate><guid>https://nuteo.info/en/tools/healthchecks/</guid><description>&lt;h1 id="healthchecksio-cron-monitoring"&gt;Healthchecks.io: cron monitoring&lt;/h1&gt;
&lt;p&gt;Cron jobs เงียบ ถ้า &lt;code&gt;restic-backup.sh&lt;/code&gt; fail เพราะ disk เต็ม ไม่มีใครบอก —
จนกว่าคุณจะต้องใช้ backup แล้วพบว่ามันว่างเปล่า&lt;/p&gt;
&lt;p&gt;Healthchecks.io แก้ปัญหานี้ cron jobs ของคุณ ping URL เมื่อเริ่มและเสร็จ
ถ้าไม่มี ping มาตามกำหนด คุณจะได้ alert&lt;/p&gt;
&lt;h2 id="ตงคา"&gt;ตั้งค่า&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;สมัครที่ &lt;a href="https://healthchecks.io"&gt;healthchecks.io&lt;/a&gt; (ฟรี 20 checks)&lt;/li&gt;
&lt;li&gt;สร้าง check:
&lt;ul&gt;
&lt;li&gt;Name: &amp;ldquo;nightly backup&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Period: 24 hours&lt;/li&gt;
&lt;li&gt;Grace: 1 hour (alert ถ้าไม่มี ping ใน 25h)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;จะได้ URL เช่น &lt;code&gt;https://hc-ping.com/UUID&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="ใชใน-bash"&gt;ใช้ใน bash&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -e
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -fsS -m &lt;span class="m"&gt;10&lt;/span&gt; --retry &lt;span class="m"&gt;5&lt;/span&gt; https://hc-ping.com/UUID/start &amp;gt; /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;restic -r b2:my-bucket:vps-snapshots backup /etc /home /srv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -fsS -m &lt;span class="m"&gt;10&lt;/span&gt; --retry &lt;span class="m"&gt;5&lt;/span&gt; https://hc-ping.com/UUID &amp;gt; /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;set -e&lt;/code&gt; ทำให้ success ping รันเฉพาะตอน success ถ้า restic fail script
จะ exit โดยไม่ ping → healthchecks.io alert คุณ&lt;/p&gt;</description></item><item><title>Production homelab: 50+ services on 3 nodes</title><link>https://nuteo.info/en/work/homelab-50-services/</link><pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate><guid>https://nuteo.info/en/work/homelab-50-services/</guid><description>&lt;h1 id="production-homelab-50-services-on-3-nodes"&gt;Production homelab: 50+ services on 3 nodes&lt;/h1&gt;
&lt;p&gt;Three Ubuntu boxes. One Caddy reverse proxy. Fifty-plus self-hosted services
running the show — email, git, CI/CD, dashboards, password manager,
photo backup, and a small Kubernetes cluster for the things that need it.&lt;/p&gt;
&lt;h2 id="the-stack"&gt;The stack&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Node&lt;/th&gt;
 &lt;th&gt;Role&lt;/th&gt;
 &lt;th&gt;Specs&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;edge-01&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Reverse proxy + firewall&lt;/td&gt;
 &lt;td&gt;2 vCPU, 4GB RAM, public IP&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;app-01&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Application workloads&lt;/td&gt;
 &lt;td&gt;8 vCPU, 32GB RAM, NVMe&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;data-01&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Databases + storage&lt;/td&gt;
 &lt;td&gt;8 vCPU, 32GB RAM, 2×4TB HDD (RAID1)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="what-runs-where"&gt;What runs where&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;edge-01&lt;/strong&gt; (public-facing):&lt;/p&gt;</description></item><item><title>Uptime monitoring with Uptime Kuma</title><link>https://nuteo.info/en/guides/uptime-monitoring/</link><pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate><guid>https://nuteo.info/en/guides/uptime-monitoring/</guid><description>&lt;h1 id="uptime-monitoring-with-uptime-kuma"&gt;Uptime monitoring with Uptime Kuma&lt;/h1&gt;
&lt;p&gt;Uptime Kuma is the self-hosted alternative to UptimeRobot / Pingdom.
It&amp;rsquo;s a single Docker container, has a beautiful UI, and supports
every monitor type I actually need.&lt;/p&gt;
&lt;h2 id="deploy"&gt;Deploy&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# docker-compose.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.8&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uptime-kuma&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;louislam/uptime-kuma:1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;uptime-kuma&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;uptime_data:/app/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;127.0.0.1:3001:3001&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Bind to localhost only&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uptime_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then expose it via Caddy on a private subdomain (e.g. &lt;code&gt;status.internal.example.com&lt;/code&gt;)
behind your WireGuard VPN.&lt;/p&gt;</description></item></channel></rss>