Gameboy-Gate — Aufnahmeprüfung für jedes Projekt

Persönliche Claude Code Skills, die auf allen Maschinen (macOS + Windows) synchronisiert werden und unter ~/.claude/skills/ leben. Stellt sicher, dass jedes Projekt auf dem Gameboy-Server die Aufnahmeprüfung besteht.

Was ist Gameboy-Gate — Aufnahmeprüfung für jedes Projekt?

Das Gameboy-Gate ist eine Sammlung von Claude Code Skills, die auf die Verwaltung und Bereitstellung von Projekten auf einem Gameboy-Server (Hetzner CAX11, ARM) mit 4 GB RAM spezialisiert ist. Es erzwingt eine strenge Aufnahmeprüfung für jedes Projekt, bevor es auf dem Server deployed wird. Die Skills automatisieren die Überprüfung des Serverzustands, die dynamische Berechnung von Ressourcenlimits (RAM, CPU), die persistente Speicherung dieser Limits in der Coolify-Datenbank, die Setzung von Umgebungsvariablen und Konfigurationen sowie einen Lasttest. Sie stellen sicher, dass Limits Deploy-Änderungen überstehen, und bieten oft gestellte Fehlerbehebungen, ein Deploy-Überlebensprotokoll und eine klare PASS/FAIL-Entscheidung. Das Tool ist für Entwickler gedacht, die Claude Code, Cursor, Codex oder ähnliche KI-Agenten verwenden, um Projekte auf ressourcenbeschränkten Servern zu verwalten. Es unterstützt macOS und Windows, da es als reine CLI-Tools und Markdown-Dateien implementiert ist.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add Cem-Tas96/claude-skills

Installed? Explore more Produktivität & Zusammenarbeit skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

🎮 Gameboy-Gate — Aufnahmeprüfung für jedes Projekt

PFLICHT: Jedes Projekt, das auf dem Gameboy (<GAMEBOY_IP>) deployed wird, muss dieses Gate bestehen — egal wer Claude nutzt, egal welches Projekt. Kein Release ohne PASS.

🔒 Verbindungsdaten lokal: Reale Werte für <GAMEBOY_IP>, <GAMEBOY_SSH>, <COOLIFY_URL>, <WATCHDOG_*> stehen in der gitignored Datei gameboy-gate/gameboy.local.md (NICHT im öffentlichen Repo). Vor jedem Gate-Lauf diese Datei lesen und die Platzhalter ersetzen.

KONTEXT: Der Gameboy

Server: Hetzner CAX11 (ARM)
IP:     <GAMEBOY_IP>          # real: siehe gameboy.local.md
SSH:    <GAMEBOY_SSH>         # real: siehe gameboy.local.md
RAM:    4GB total
CPU:    2x vCPU (ARM Neoverse-N1 @ 2.0GHz)
Disk:   40GB SSD
OS:     Ubuntu (Docker + Coolify)
Coolify UI: <COOLIFY_URL>     # real: siehe gameboy.local.md

Feste Overhead-Reservierung (NIE unterschreiten):

OS + Kernel:              400MB
Coolify (inkl. DB, Redis, Proxy, Sentinel): 900MB
Safety-Buffer:            200MB
──────────────────────────────
FREI für Projekte:       ~2500MB  (aus 4096MB Gesamt)

Projekte, die bereits laufen (Stand 26.06.2026 — reale Namen siehe gameboy.local.md):

  • app-workflow (Activepieces): 1536MB limit / 768MB reservation
  • app-billing (Next.js): 512MB limit / 256MB reservation
  • app-landing (Static): 256MB limit / 128MB reservation
  • Belegt: 2304MB limit / 1152MB reservation

GATE-ABLAUF (in dieser Reihenfolge, kein Überspringen)

Phase G0: Server-Snapshot holen

Immer zuerst — das ist die Wahrheit über den aktuellen Server-Zustand:

<GAMEBOY_SSH> "
free -h &&
docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}' &&
df -h / &&
docker inspect \$(docker ps -q) --format '{{.Name}}: mem={{.HostConfig.Memory}}' 2>/dev/null | sort
"

Daraus ableiten:

  • TOTAL_RAM_MB (fast immer 3840MB = 3.7GB als Bytes)
  • COOLIFY_OVERHEAD_MB = Summe aller Coolify-eigenen Container (coolify, coolify-db, coolify-redis, coolify-sentinel, coolify-proxy) aktuell
  • RUNNING_PROJECTS_MB = Summe der mem_limit aller laufenden Projekt-Container
  • AVAILABLE_MB = TOTAL_RAM_MB - 400 (OS) - COOLIFY_OVERHEAD_MB - RUNNING_PROJECTS_MB - 200 (safety)

Phase G1: Limit-Berechnung (KI-gesteuert, keine Hardcodes)

Basierend auf dem Projekt-Typ berechnet die KI die richtigen Limits:

PROJEKT-TYP ERKENNEN:
- Node.js / Next.js App       → BASE_MB = 350, WORKER_OVERHEAD = 50 pro Worker
- Python / Django / FastAPI   → BASE_MB = 200, WORKER_OVERHEAD = 30 pro Worker
- Go / Rust Service           → BASE_MB = 80,  WORKER_OVERHEAD = 20 pro Worker
- Static-Site (nginx/caddy)   → BASE_MB = 64,  WORKER_OVERHEAD = 0
- Postgres                    → BASE_MB = 128, WORKER_OVERHEAD = 10 pro Connection
- Redis                       → BASE_MB = 64,  WORKER_OVERHEAD = 0
- Background-Worker (queues)  → BASE_MB = 200, WORKER_OVERHEAD = 50 pro Worker
- AI/LLM-Service (lokal)      → BASE_MB = 800+ (mindestens 2GB wenn Modell lokal)

Limit-Formeln:

MEM_LIMIT_MB    = min(AVAILABLE_MB * 0.8, BASE_MB * 3)   -- 80% des Verfügbaren, max 3x Base
MEM_RESERVATION = MEM_LIMIT_MB * 0.5                      -- 50% als garantiertes Minimum
MEM_SWAP_MB     = MEM_LIMIT_MB * 1.4                      -- 40% Swap-Overhead erlaubt
CPU_LIMIT       = round(2.0 * (MEM_LIMIT_MB / AVAILABLE_MB), 1)  -- proportional zu RAM-Anteil

NODE_OPTIONS_MB = floor(MEM_LIMIT_MB * 0.70)              -- V8 Heap = 70% des Container-Limits
UV_THREADPOOL_SIZE = min(8, cpu_count * 2)                -- libuv Threads

Validation-Checks VOR dem Setzen:

  • MEM_LIMIT_MB >= 128 (Minimum um zu starten)
  • AVAILABLE_MB - MEM_LIMIT_MB >= 200 (Safety-Buffer bleibt übrig)
  • Summe aller mem_limits ≤ 3500MB (kein Overcommit über 85% physischer RAM)
  • Wenn Validation fehlschlägt → BLOCKIERT, Cem informieren

Phase G2: Limits permanent in Coolify DB setzen

Das ist der kritische Fix gegen deploy-Regressionen. Coolify regeneriert docker-compose.yaml bei jedem Deploy aus seiner eigenen DB. Nur wenn die Limits in der DB stehen, überleben sie Deploys.

Wenn die App-UUID bekannt ist:

<GAMEBOY_SSH> "
docker exec coolify-db psql -U coolify -d coolify -c \"
UPDATE applications SET
  limits_memory = '<MEM_LIMIT_MB>m',
  limits_memory_swap = '<MEM_SWAP_MB>m',
  limits_memory_swappiness = 10,
  limits_memory_reservation = '<MEM_RESERVATION>m',
  limits_cpus = '<CPU_LIMIT>',
  limits_cpu_shares = 1024
WHERE uuid = '<APP_UUID>';
SELECT uuid, name, limits_memory, limits_cpus FROM applications WHERE uuid='<APP_UUID>';
\"
"

UUID aus Coolify-URL holen: <COOLIFY_URL>/project/.../application/<UUID>/...

Wenn UUID unbekannt → via DB suchen:

docker exec coolify-db psql -U coolify -d coolify -c "SELECT uuid, name FROM applications ORDER BY created_at DESC LIMIT 10;"

Phase G3: Environment-Variablen setzen

In .env des Projekts (via echo >> .env oder Coolify UI):

# Für Node.js/Next.js:
NODE_OPTIONS=--max-old-space-size=<NODE_OPTIONS_MB>
UV_THREADPOOL_SIZE=<UV_THREADPOOL_SIZE>

# Für Workflow-Apps (Activepieces etc.):
AP_MAX_CONCURRENT_JOBS=5     # verhindert Sandbox-Explosion
AP_TRIGGER_DEFAULT_POLL_INTERVAL=15  # reduziert DB-Baseline-Last

# Für alle Apps:
# (kein DEBUG logging in production!)
NODE_ENV=production

WICHTIG: Kein Zeilenumbruch-Bug! Jede Variable auf eigener Zeile prüfen:

grep -c '^[A-Z_].*=' .env  # sollte gleich sein wie die Anzahl Variablen
# Keine Zeile darf zwei '=' enthalten außer in Werten
awk -F= 'NF>2 && $1 ~ /^[A-Z_]/' .env && echo "BUG: doppelte = in Zeile!"

Phase G4: Traefik Rate-Limiting aktivieren

Für JEDES neue öffentliche Service:

<GAMEBOY_SSH> "
cat > /data/coolify/proxy/dynamic/rate-limit-global.yaml << 'EOF'
http:
  middlewares:
    rate-limit-strict:
      rateLimit:
        average: 60
        burst: 120
        period: 1s
        sourceCriterion:
          ipStrategy:
            depth: 1
    rate-limit-api:
      rateLimit:
        average: 30
        burst: 60
        period: 1s
        sourceCriterion:
          ipStrategy:
            depth: 1
EOF
echo 'Rate-limiting config written'
"

Traefik picked das automatisch auf (watch=true) — kein Restart nötig.

Middleware dann zum App-Router hinzufügen (in der App-spezifischen Traefik-Konfig):

middlewares:
  - rate-limit-strict

Phase G5: File Descriptor Limits setzen

Docker daemon muss Limits kennen. Einmalig pro Gameboy-Setup, bleibt persistent:

<GAMEBOY_SSH> "
# Prüfen ob already set
grep -q 'nofile' /etc/docker/daemon.json && echo 'already set' || {
  # Backup und Update
  cp /etc/docker/daemon.json /etc/docker/daemon.json.bak.\$(date +%Y%m%d)
  python3 -c \"
import json
with open('/etc/docker/daemon.json') as f: d = json.load(f)
d['default-ulimits'] = {'nofile': {'Name': 'nofile', 'Hard': 65536, 'Soft': 65536}}
with open('/etc/docker/daemon.json', 'w') as f: json.dump(d, f, indent=2)
print('daemon.json updated')
  \"
  # ACHTUNG: systemctl restart docker startet alle Container neu!
  # Nur wenn kein aktiver Traffic → mit Cem abstimmen
  echo 'NEEDS: systemctl restart docker (kurzer Downtime!)'
}
"

Phase G6: Load-Test (die eigentliche Aufnahmeprüfung)

50 concurrent users, 60 Sekunden:

# Test 1: Basis-Erreichbarkeit
curl -o /dev/null -s -w "HTTP=%{http_code} %{time_total}s\n" https://<APP_URL>/

# Test 2: 50 concurrent HTTP-Requests (30s)
/usr/sbin/ab -n 3000 -c 50 -t 30 -r -k https://<APP_URL>/ 2>&1 | grep -E "Requests|Failed|Time per|50%|90%|99%"

# Test 3: Memory während Last monitoren
<GAMEBOY_SSH> "for i in \$(seq 1 6); do sleep 10; docker stats --no-stream --format '{{.Name}}: {{.MemUsage}} {{.MemPerc}} CPU={{.CPUPerc}}' | grep -v NAMES; echo '---'; done" &
MONITOR_PID=\$!
/usr/sbin/ab -n 5000 -c 50 -t 55 -r https://<APP_URL>/ 2>/dev/null
wait \$MONITOR_PID

Phase G7: PASS/FAIL Kriterien

PASS (alle müssen erfüllt sein):

KriteriumGrenzwert
HTTP Status2xx oder 3xx (kein 502/503/504)
p99 Latenz< 3000ms
Memory-Wachstumstabil (< +20% zwischen t=0 und t=60s)
Memory bleibt unter Limit< 90% des Container-Limits
Keine Failed-Requests< 0.1%
Server nach Test erreichbarHTTP 200/302 auf allen URLs
CPU nach Test< 80% sustained

FAIL (einer reicht für BLOCKIERT):

  • Irgendein Container ist nach dem Test down/restarted
  • Memory hat Limit erreicht (> 95%)
  • Failed requests > 1%
  • p99 > 5000ms
  • Watchdog hat während des Tests eingegriffen (prüfen: <WATCHDOG_LOG>)

Phase G8: Gate-Ergebnis

═══════════════════════════════════════════════════════
🎮 GAMEBOY-GATE ERGEBNIS
═══════════════════════════════════════════════════════
Projekt:     [Name]
App-UUID:    [Coolify UUID]
Test-Zeit:   [Datum/Uhrzeit]

LIMIT-BERECHNUNG:
  Server-RAM:              3840MB
  Coolify-Overhead:        [n]MB (gemessen)
  Andere Projekte:         [n]MB (Summe Limits)
  Verfügbar:               [n]MB
  Zugewiesen:              MEM=[n]m, CPU=[n], NODE_OPTIONS=[n]MB

CHECKS:
  ✅/❌ Coolify DB Limits persistent gesetzt
  ✅/❌ NODE_OPTIONS korrekt (kein Newline-Bug)
  ✅/❌ Rate-Limiting aktiv
  ✅/❌ File Descriptor Limit 65536
  ✅/❌ Redis maxmemory policy gesetzt (wenn Redis)
  ✅/❌ Load-Test 50c/30s bestanden

LOAD-TEST:
  Req/s:    [n]     (PASS wenn > 10)
  p50:      [n]ms
  p99:      [n]ms   (PASS wenn < 3000ms)
  Failed:   [n]%    (PASS wenn < 0.1%)
  Mem-Peak: [n]MB   (PASS wenn < 90% des Limits)

═══════════════════════════════════════════════════════
URTEIL: ✅ PASS — RELEASE ERLAUBT
       ❌ FAIL — RELEASE BLOCKIERT ([Grund])
═══════════════════════════════════════════════════════

HÄUFIGE FAIL-GRÜNDE UND FIXES

FAIL: NODE_OPTIONS nicht gesetzt

# Prüfen:
docker exec <container> printenv NODE_OPTIONS
# Fix:
grep -n 'NODE_OPTIONS' .env  # auf Newline-Bug prüfen
# Bug: HOST=0.0.0.0NODE_OPTIONS=... → Fix:
sed -i 's/HOST=0.0.0.0NODE_OPTIONS=/HOST=0.0.0.0\nNODE_OPTIONS=/' .env

FAIL: Container OOM nach Test

Root-Cause fast immer eines von:

  1. NODE_OPTIONS nicht gesetzt → V8 nimmt Container-RAM komplett
  2. Sandbox-Prozesse (AP, Code-Execution) ohne Concurrency-Limit
  3. Memory-Leak im App-Code (seltener)
# Prüfen ob Restart stattfand:
docker inspect <container> --format '{{.RestartCount}}'
# Prüfen ob OOM-Kill:
dmesg | grep -i 'oom\|killed' | tail -10

FAIL: p99 > 3000ms

  1. Heavy API-Endpoint ohne Caching → Redis-Cache implementieren
  2. PG-Connection-Pool erschöpft → MAX_CONCURRENT_REQUESTS oder Pool-Size erhöhen
  3. Forward-Auth-Overhead pro Request → JWT-Caching einbauen

FAIL: Failed Requests > 0.1%

  1. Traefik-Timeout kürzer als App-Response → Traefik-Timeouts erhöhen
  2. FD-Limit erschöpft → Schritt G5 (ulimits)
  3. PG max_connections erschöpft → max_connections=200 in PG setzen

DEPLOY-RESILIENZ: Das Deploy-Überlebens-Protokoll

Jeder Coolify-Deploy muss folgendes garantieren damit der Server nicht abstürzt:

Vor jedem Deploy prüfen:

<GAMEBOY_SSH> "
# Freier RAM muss > 800MB sein
FREE=\$(free -m | awk '/^Mem:/{print \$7}')
echo \"Available: \${FREE}MB\"
[ \$FREE -lt 800 ] && echo 'WARNUNG: Zu wenig RAM für Deploy!' || echo 'RAM OK für Deploy'
# Disk muss > 5GB frei sein (Image-Pull braucht Platz)
DISK=\$(df / | awk 'NR==2{print \$4}')
echo \"Disk free: \$((DISK/1024))MB\"
[ \$DISK -lt 5242880 ] && echo 'WARNUNG: Zu wenig Disk!' || echo 'Disk OK'
"

Was Coolify bei Deploy macht (und was wir sicherstellen müssen):

  1. Coolify liest seine DB → generiert docker-compose.yaml mit den in DB gesetzten Limits (G2 → persistent ✅)
  2. Container stoppt → kurzer Downtime
  3. Neuer Container startet → Healthcheck wartet bis App bereit
  4. Traefik bekommt neuen Backend-Endpunkt

Healthcheck-Konfiguration (in Coolify UI oder direkt in DB):

docker exec coolify-db psql -U coolify -d coolify -c "
UPDATE applications SET
  health_check_enabled = true,
  health_check_path = '/api/v1/flags',  -- oder '/', '/health', '/ping'
  health_check_port = '80',
  health_check_interval = 15,
  health_check_timeout = 5,
  health_check_retries = 3,
  health_check_start_period = 90  -- 90s Startzeit für schwere Apps
WHERE uuid = '<APP_UUID>';
"

MEMORY-BUDGET-TABELLE (Stand 26.06.2026)

GAMEBOY 4GB RAM — BUDGET-ÜBERSICHT
════════════════════════════════════════════════════════════
System/OS:              400MB (fix)
Coolify-Stack:          ~950MB (coolify+db+redis+sentinel+proxy)
────────────────────────────────────────────────────────────
VERFÜGBAR FÜR PROJEKTE: ~2650MB
════════════════════════════════════════════════════════════
app-workflow:          1536MB limit / 768MB reservation
app-billing:            512MB limit / 256MB reservation
app-landing:            256MB limit / 128MB reservation
────────────────────────────────────────────────────────────
NOCH FREI:             ~346MB limit-Spielraum
════════════════════════════════════════════════════════════

WARNUNG: Der Gameboy ist zu ~91% seiner Limit-Kapazität belegt.
Neues Projekt > 300MB braucht Rebalancing (existierendes
Projekt verkleinern oder Coolify-Stack optimieren).

Wenn ein neues Projekt mehr als 200MB braucht → erst prüfen ob landing (256MB) auf 128MB reduziert werden kann (statische Site, braucht wenig RAM).


CHECKLISTE FÜR JEDEN DEPLOY (Quick Reference)

VOR DEPLOY:
  [ ] Freier RAM > 800MB
  [ ] Disk > 5GB frei
  [ ] Coolify DB Limits gesetzt (nicht 0!)
  [ ] NODE_OPTIONS in .env korrekt (keine Zeilenumbruch-Bugs)
  [ ] Rate-Limiting für öffentliche Endpoints konfiguriert

NACH DEPLOY:
  [ ] Container läuft (docker ps → Up X seconds (healthy))
  [ ] URL erreichbar (HTTP 2xx/3xx)
  [ ] NODE_OPTIONS aktiv (docker exec <c> printenv NODE_OPTIONS)
  [ ] Memory stabil (docker stats → nicht an Limit)
  [ ] Kein OOM-Kill (dmesg | grep -i ...)

[truncated for translation]

Verwandte Skills