Playbook: vps-i1 OOM Outage
What triggers this
A process running under claude-runner (UID 1000) on vps-i1 consumes all available RAM,
causing the OOM killer to fire, which destabilizes Docker networking (veth storm in kernel log).
Most commonly triggered by a Claude Code session invoked via SSH from n8n (BrandPilot AI workflow)
that spawns a large Python process for brand content generation.
How to confirm
# Kernel OOM log
ssh root@217.154.82.162 "dmesg | grep -E 'oom|killed' | tail -20"
# Docker networking storm (many veth interfaces)
ssh root@217.154.82.162 "ip link | grep -c veth"
# Containers down
ssh root@217.154.82.162 "cd /opt/p24-infra/monitoring && docker compose ps --format 'table {{.Name}}\t{{.Status}}'"Step-by-step fix
-
SSH in (usually still possible after OOM clears, Docker storm may prevent it briefly — wait 60s):
ssh root@217.154.82.162 -
Stop runners to prevent new Claude sessions spawning while recovering:
systemctl stop actions.runner.radieu-p24-infra.ionos-localhost-1 systemctl stop actions.runner.radieu-p24-infra.ionos-localhost-2 -
Bring monitoring stack back up:
cd /opt/p24-infra/monitoring && docker compose up -d docker compose ps -
Verify all containers healthy (caddy, grafana, prometheus, alertmanager, exporters).
-
Start runners again:
systemctl start actions.runner.radieu-p24-infra.ionos-localhost-1 systemctl start actions.runner.radieu-p24-infra.ionos-localhost-2
5b. Verify the sops-credential-liveness cron survived (#5066) — see
docs/playbooks/vps-i1-outage.md step 7 / docs/playbooks/sops-credential-liveness.md
§Escalation for the check command and fix. An OOM-triggered reboot is exactly the kind of
event that has silently dropped this cron entry before without affecting other crons.
- Check memory headroom:
free -h ps aux --sort=-%mem | head -10
Escalation path
If SSH is completely unreachable:
- IONOS control panel → VNC console → log in as root
- If the host is frozen: IONOS control panel → Reboot
- After reboot: repeat step 3 (monitoring stack) and step 5 (runners)
Prevention
- Runners are capped at 2GB (
MemoryMax=2Gin systemd unit) — if a job exceeds this, the cgroup kills the job, not the host - BrandPilot AI workflow now runs on bms-4 (32GB RAM) instead of vps-i1, eliminating the main trigger
- Claude default model on vps-i1 should be Sonnet (not Opus) — Opus sessions use 2–3×
more memory. Check:
su -s /bin/bash claude-runner -c 'cat ~/.claude.json | grep model'
Key files
- Runner systemd unit:
/etc/systemd/system/actions.runner.radieu-p24-infra.ionos-localhost-2.service - Monitoring compose:
/opt/p24-infra/monitoring/docker-compose.yml
Incident log
- 2026-06-22 ~21:10 UTC: claude-runner python3 PID 501374, 5.9GB RSS, OOM killed. Trigger: BrandPilot brand wizard SSHing into vps-i1 and running Claude (Opus) for ~30s of parallel brand generation. Recovery: 15 min, all containers restored. Mitigations applied: 2GB runner cap, moved BrandPilot AI to bms-4.
Audit Log — Log to infra_operations
After this operation completes, log it to the infra_operations audit table.
Python (Linux server — bms-4, vps-i1, vps-h1, or similar):
import sys
sys.path.insert(0, '/opt/p24-infra')
from scripts.lib.log_op import log_op
log_op(
actor="claude", # "radieu" for manual human ops, "claude" for agent
op_type="restart",
resource="vps-i1",
result="success", # "success" | "failed" | "skipped"
detail="vps-i1 OOM outage resolved — memory limits adjusted, monitoring stack restarted",
env="vps-i1",
gh_issue=2730,
)PowerShell (Windows dev machine):
$env:SUPABASE_URL = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_URL=").ToString().Split("=",2)[1].Trim()
$env:SUPABASE_SERVICE_KEY = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_SERVICE_KEY=").ToString().Split("=",2)[1].Trim()
python -c "
import os, sys
sys.path.insert(0, 'C:/code_2026/p24-infra')
from scripts.lib.log_op import log_op
log_op('claude', 'restart', 'vps-i1', 'success', 'vps-i1 OOM outage resolved — memory limits adjusted, monitoring stack restarted', 'vps-i1')
"
$env:SUPABASE_URL = ''; $env:SUPABASE_SERVICE_KEY = ''