<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ops on nuteo.info</title><link>https://nuteo.info/en/tags/ops/</link><description>Recent content in Ops 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/ops/index.xml" rel="self" type="application/rss+xml"/><item><title>Postmortem: the day Postgres took down production</title><link>https://nuteo.info/en/blog/postmortem-postgres-outage/</link><pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate><guid>https://nuteo.info/en/blog/postmortem-postgres-outage/</guid><description>&lt;h1 id="postmortem-the-day-postgres-took-down-production"&gt;Postmortem: the day Postgres took down production&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: A missing index on a foreign key column, combined with a cron
job running during peak hours, caused a 47-minute outage on a billing API.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="timeline-all-times-utc"&gt;Timeline (all times UTC)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;14:00&lt;/strong&gt; — Cron job starts: &lt;code&gt;cleanup_old_invoices.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;14:02&lt;/strong&gt; — Customer reports slow checkout&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;14:05&lt;/strong&gt; — PagerDuty alert: API latency p99 &amp;gt; 5s&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;14:08&lt;/strong&gt; — On-call engineer (me) opens laptop&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;14:14&lt;/strong&gt; — Identify: Postgres CPU 100%, hundreds of &lt;code&gt;DELETE&lt;/code&gt; queries waiting&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;14:22&lt;/strong&gt; — Kill the cron job manually → queries drain in ~30s&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;14:25&lt;/strong&gt; — Service recovering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;14:47&lt;/strong&gt; — Full recovery, replication caught up&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Total impact&lt;/strong&gt;: 47 minutes of degraded service, ~120 failed transactions.&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>Server inventory &amp; deployment automation</title><link>https://nuteo.info/en/work/server-inventory/</link><pubDate>Mon, 03 Aug 2026 00:00:00 +0000</pubDate><guid>https://nuteo.info/en/work/server-inventory/</guid><description>&lt;h1 id="server-inventory--deployment-automation"&gt;Server inventory &amp;amp; deployment automation&lt;/h1&gt;
&lt;p&gt;When you have more than 5 servers, you stop remembering which one runs what.
This tool fixes that. Single source of truth for: server, role, deploy status,
uptime, and alerts.&lt;/p&gt;
&lt;h2 id="what-it-does"&gt;What it does&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📋 &lt;strong&gt;Inventory&lt;/strong&gt; — name, IP, role, environment, tags&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Deploy queue&lt;/strong&gt; — what&amp;rsquo;s deploying where, status&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Dashboard&lt;/strong&gt; — uptime, last deploy, alerts&lt;/li&gt;
&lt;li&gt;🔔 &lt;strong&gt;Notifications&lt;/strong&gt; — Slack/Telegram when deployments fail&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="stack"&gt;Stack&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python&lt;/strong&gt; (FastAPI) for the API&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLite&lt;/strong&gt; for local store (Postgres optional)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTMX&lt;/strong&gt; for the dashboard (no React, no build step)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;systemd&lt;/strong&gt; + cron for agent-side collection&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="architecture"&gt;Architecture&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ each host │ │ central │ │ dashboard │
│ │ │ API │ │ │
│ agent.py ──┼──▶ │ FastAPI ────┼──▶ │ /dashboard │
│ (cron 5m) │ │ SQLite │ │ (HTMX) │
└─────────────┘ └──────────────┘ └─────────────┘
 │
 ▼
 ┌─────────────┐
 │ Telegram │
 │ alerts │
 └─────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The agent is a 50-line Python script that runs via cron every 5 minutes,
collects &lt;code&gt;uptime&lt;/code&gt;, &lt;code&gt;df&lt;/code&gt;, &lt;code&gt;docker ps&lt;/code&gt;, and POSTs to the central API.&lt;/p&gt;</description></item></channel></rss>