Cem-Tas96/claude-skills

Habilidades personales de Claude Code, sincronizadas en todas mis máquinas (macOS + Windows). Reside en ~/.claude/skills/.

¿Qué es claude-skills?

Esta habilidad de Claude Code, llamada 'Gameboy-Gate', implementa un riguroso proceso de 'puerta de admisión' para cualquier proyecto desplegado en un servidor personal apodado 'Gameboy' (un Hetzner CAX11 ARM con 4GB de RAM, ejecutando Ubuntu, Docker y Coolify). Su propósito principal es prevenir la inestabilidad del servidor y las regresiones de despliegue al forzar una validación sistemática antes de cada lanzamiento. La habilidad automatiza múltiples fases: desde obtener un snapshot del servidor y calcular límites de memoria basados en el tipo de proyecto, hasta establecer esos límites de forma persistente en la base de datos de Coolify, configurar variables de entorno, activar rate-limiting en Traefik, fijar límites de descriptores de archivo en Docker y ejecutar pruebas de carga de estrés. Utiliza un estricto conjunto de criterios de aprobación/reprobación (basados en latencia p99, uso de memoria, fallos de solicitudes, etc.) y proporciona diagnósticos detallados para fallos comunes. Diseñada para ejecutarse en Claude Code, esta habilidad garantiza que ningún proyecto consuma más recursos de los disponibles, evitando que el servidor se quede sin memoria (OOM) y manteniendo la estabilidad del servicio en todos los despliegues.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add Cem-Tas96/claude-skills

Installed? Explore more Productividad y colaboración skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

🎮 Gameboy-Gate — Prueba de admisión para cada proyecto

OBLIGATORIO: Todo proyecto desplegado en el Gameboy (<GAMEBOY_IP>) debe pasar este Gate — sin importar quién use Claude, ni qué proyecto sea. Sin lanzamiento sin PASS.

🔒 Datos de conexión local: Los valores reales para <GAMEBOY_IP>, <GAMEBOY_SSH>, <COOLIFY_URL>, <WATCHDOG_*> se encuentran en el archivo ignorado por git gameboy-gate/gameboy.local.md (NO en el repositorio público). Antes de cada ejecución del Gate, leer este archivo y reemplazar los marcadores de posición.

CONTEXTO: El Gameboy

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

Reserva fija de overhead (NUNCA reducir por debajo de esto):

SO + Kernel:              400MB
Coolify (incl. DB, Redis, Proxy, Sentinel): 900MB
Buffer de seguridad:      200MB
──────────────────────────────
LIBRE para proyectos:   ~2500MB  (de 4096MB totales)

Proyectos ya en ejecución (al 26/06/2026 — nombres reales en gameboy.local.md):

  • app-workflow (Activepieces): límite 1536MB / reserva 768MB
  • app-billing (Next.js): límite 512MB / reserva 256MB
  • app-landing (Estático): límite 256MB / reserva 128MB
  • Ocupado: 2304MB límite / 1152MB reserva

FLUJO DEL GATE (en este orden, sin saltos)

Fase G0: Obtener snapshot del servidor

Siempre primero — esta es la verdad sobre el estado actual del servidor:

<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
"

De esto derivar:

  • TOTAL_RAM_MB (casi siempre 3840MB = 3.7GB en bytes)
  • COOLIFY_OVERHEAD_MB = suma de todos los contenedores propios de Coolify (coolify, coolify-db, coolify-redis, coolify-sentinel, coolify-proxy) actual
  • RUNNING_PROJECTS_MB = suma de mem_limit de todos los contenedores de proyectos en ejecución
  • AVAILABLE_MB = TOTAL_RAM_MB - 400 (SO) - COOLIFY_OVERHEAD_MB - RUNNING_PROJECTS_MB - 200 (seguridad)

Fase G1: Cálculo de límites (guiado por IA, sin valores fijos)

Basado en el tipo de proyecto, la IA calcula los límites correctos:

DETECTAR TIPO DE PROYECTO:
- App Node.js / Next.js       → BASE_MB = 350, WORKER_OVERHEAD = 50 por worker
- Python / Django / FastAPI   → BASE_MB = 200, WORKER_OVERHEAD = 30 por worker
- Servicio Go / Rust          → BASE_MB = 80,  WORKER_OVERHEAD = 20 por worker
- Sitio estático (nginx/caddy) → BASE_MB = 64,  WORKER_OVERHEAD = 0
- Postgres                    → BASE_MB = 128, WORKER_OVERHEAD = 10 por conexión
- Redis                       → BASE_MB = 64,  WORKER_OVERHEAD = 0
- Worker en segundo plano (colas) → BASE_MB = 200, WORKER_OVERHEAD = 50 por worker
- Servicio AI/LLM (local)     → BASE_MB = 800+ (al menos 2GB si el modelo es local)

Fórmulas de límite:

MEM_LIMIT_MB    = min(AVAILABLE_MB * 0.8, BASE_MB * 3)   -- 80% de lo disponible, máximo 3x Base
MEM_RESERVATION = MEM_LIMIT_MB * 0.5                      -- 50% como mínimo garantizado
MEM_SWAP_MB     = MEM_LIMIT_MB * 1.4                      -- 40% de overhead de swap permitido
CPU_LIMIT       = round(2.0 * (MEM_LIMIT_MB / AVAILABLE_MB), 1)  -- proporcional a la porción de RAM

NODE_OPTIONS_MB = floor(MEM_LIMIT_MB * 0.70)              -- V8 Heap = 70% del límite del contenedor
UV_THREADPOOL_SIZE = min(8, cpu_count * 2)                -- Hilos libuv

Validaciones ANTES de establecer:

  • MEM_LIMIT_MB >= 128 (mínimo para iniciar)
  • AVAILABLE_MB - MEM_LIMIT_MB >= 200 (el buffer de seguridad permanece)
  • Suma de todos los mem_limits ≤ 3500MB (sin sobrecompromiso superior al 85% de la RAM física)
  • Si la validación falla → BLOQUEADO, informar a Cem

Fase G2: Establecer límites de forma permanente en la base de datos de Coolify

Esta es la solución crítica contra las regresiones de despliegue. Coolify regenera docker-compose.yaml en cada despliegue desde su propia base de datos. Solo si los límites están en la base de datos sobrevivirán a los despliegues.

Si se conoce el UUID de la aplicación:

<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>';
\"
"

Obtener UUID de la URL de Coolify: <COOLIFY_URL>/project/.../application/<UUID>/...

Si el UUID es desconocido → buscar en la base de datos:

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

Fase G3: Establecer variables de entorno

En .env del proyecto (mediante echo >> .env o la interfaz de Coolify):

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

# Para aplicaciones de flujo de trabajo (Activepieces, etc.):
AP_MAX_CONCURRENT_JOBS=5     # evita la explosión de sandboxes
AP_TRIGGER_DEFAULT_POLL_INTERVAL=15  # reduce la carga basal de la base de datos

# Para todas las aplicaciones:
# (¡sin registro DEBUG en producción!)
NODE_ENV=production

IMPORTANTE: ¡Sin errores de salto de línea! Verificar cada variable en su propia línea:

grep -c '^[A-Z_].*=' .env  # debería ser igual al número de variables
# Ninguna línea debe contener dos '=' excepto en valores
awk -F= 'NF>2 && $1 ~ /^[A-Z_]/' .env && echo "ERROR: ¡doble = en línea!"

Fase G4: Activar limitación de tasa en Traefik

Para CADA nuevo servicio público:

<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 'Configuración de limitación de tasa escrita'
"

Traefik lo recoge automáticamente (watch=true) — no se necesita reinicio.

Luego agregar el middleware al enrutador de la aplicación (en la configuración de Traefik específica de la aplicación):

middlewares:
  - rate-limit-strict

Fase G5: Establecer límites de descriptores de archivo

El daemon de Docker debe conocer los límites. Una vez por configuración del Gameboy, permanece persistente:

<GAMEBOY_SSH> "
# Verificar si ya está establecido
grep -q 'nofile' /etc/docker/daemon.json && echo 'ya establecido' || {
  # Copia de seguridad y actualización
  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 actualizado')
  \"
  # ATENCIÓN: systemctl restart docker reinicia todos los contenedores.
  # Solo si no hay tráfico activo → coordinar con Cem
  echo 'NECESITA: systemctl restart docker (¡pequeño tiempo de inactividad!)'
}
"

Fase G6: Prueba de carga (la verdadera prueba de admisión)

50 usuarios concurrentes, 60 segundos:

# Prueba 1: Accesibilidad básica
curl -o /dev/null -s -w "HTTP=%{http_code} %{time_total}s\n" https://<APP_URL>/

# Prueba 2: 50 solicitudes HTTP concurrentes (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%"

# Prueba 3: Monitorear memoria durante la carga
<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

Fase G7: Criterios de APROBACIÓN/RECHAZO

APROBADO (todos deben cumplirse):

CriterioLímite
Estado HTTP2xx o 3xx (sin 502/503/504)
Latencia p99< 3000ms
Crecimiento de memoriaestable (< +20% entre t=0 y t=60s)
Memoria permanece por debajo del límite< 90% del límite del contenedor
Sin solicitudes fallidas< 0.1%
Servidor accesible después de la pruebaHTTP 200/302 en todas las URLs
CPU después de la prueba< 80% sostenido

RECHAZADO (uno es suficiente para BLOQUEAR):

  • Algún contenedor está caído/reiniciado después de la prueba
  • La memoria alcanzó el límite (> 95%)
  • Solicitudes fallidas > 1%
  • p99 > 5000ms
  • El watchdog intervino durante la prueba (verificar: <WATCHDOG_LOG>)

Fase G8: Resultado del Gate

═══════════════════════════════════════════════════════
🎮 RESULTADO DEL GATE DEL GAMEBOY
═══════════════════════════════════════════════════════
Proyecto:     [Nombre]
App-UUID:     [UUID de Coolify]
Hora de prueba: [Fecha/Hora]

CÁLCULO DE LÍMITES:
  RAM del servidor:              3840MB
  Overhead de Coolify:           [n]MB (medido)
  Otros proyectos:               [n]MB (suma de límites)
  Disponible:                    [n]MB
  Asignado:                      MEM=[n]m, CPU=[n], NODE_OPTIONS=[n]MB

VERIFICACIONES:
  ✅/❌ Límites de BD Coolify persistentes
  ✅/❌ NODE_OPTIONS correcto (sin error de salto de línea)
  ✅/❌ Limitación de tasa activa
  ✅/❌ Límite de descriptor de archivo 65536
  ✅/❌ Política maxmemory de Redis establecida (si es Redis)
  ✅/❌ Prueba de carga 50c/30s superada

PRUEBA DE CARGA:
  Req/s:    [n]     (APROBADO si > 10)
  p50:      [n]ms
  p99:      [n]ms   (APROBADO si < 3000ms)
  Fallos:   [n]%    (APROBADO si < 0.1%)
  Pico Mem: [n]MB   (APROBADO si < 90% del límite)

═══════════════════════════════════════════════════════
VEREDICTO: ✅ APROBADO — LANZAMIENTO PERMITIDO
           ❌ RECHAZADO — LANZAMIENTO BLOQUEADO ([Motivo])
═══════════════════════════════════════════════════════

CAUSAS FRECUENTES DE RECHAZO Y SOLUCIONES

RECHAZO: NODE_OPTIONS no establecido

# Verificar:
docker exec <contenedor> printenv NODE_OPTIONS
# Solución:
grep -n 'NODE_OPTIONS' .env  # verificar error de salto de línea
# Error: HOST=0.0.0.0NODE_OPTIONS=... → Solución:
sed -i 's/HOST=0.0.0.0NODE_OPTIONS=/HOST=0.0.0.0\nNODE_OPTIONS=/' .env

RECHAZO: Contenedor OOM después de la prueba

La causa raíz es casi siempre una de:

  1. NODE_OPTIONS no establecido → V8 toma toda la RAM del contenedor
  2. Procesos sandbox (AP, ejecución de código) sin límite de concurrencia
  3. Fuga de memoria en el código de la aplicación (menos común)
# Verificar si hubo reinicio:
docker inspect <contenedor> --format '{{.RestartCount}}'
# Verificar si hubo OOM-Kill:
dmesg | grep -i 'oom\|killed' | tail -10

RECHAZO: p99 > 3000ms

  1. Endpoint API pesado sin caché → implementar caché Redis
  2. Pool de conexiones PG agotado → aumentar MAX_CONCURRENT_REQUESTS o tamaño del pool
  3. Sobrecarga de Forward-Auth por solicitud → implementar caché JWT

RECHAZO: Solicitudes fallidas > 0.1%

  1. Timeout de Traefik más corto que la respuesta de la aplicación → aumentar timeouts de Traefik
  2. Límite FD agotado → Paso G5 (ulimits)
  3. max_connections de PG agotado → establecer max_connections=200 en PG

RESILIENCIA DE DESPLIEGUE: El protocolo de supervivencia del despliegue

Cada despliegue de Coolify debe garantizar lo siguiente para que el servidor no se bloquee:

Antes de cada despliegue verificar:

<GAMEBOY_SSH> "
# La RAM libre debe ser > 800MB
FREE=\$(free -m | awk '/^Mem:/{print \$7}')
echo "Disponible: \${FREE}MB"
[ \$FREE -lt 800 ] && echo 'ADVERTENCIA: ¡Poca RAM para el despliegue!' || echo 'RAM OK para el despliegue'
# El disco debe tener > 5GB libres (la descarga de imagen necesita espacio)
DISK=\$(df / | awk 'NR==2{print \$4}')
echo "Disco libre: \$((DISK/1024))MB"
[ \$DISK -lt 5242880 ] && echo 'ADVERTENCIA: ¡Poco disco!' || echo 'Disco OK'
"

Lo que hace Coolify durante el despliegue (y lo que debemos asegurar):

  1. Coolify lee su base de datos → genera docker-compose.yaml con los límites establecidos en la base de datos (G2 → persistente ✅)
  2. El contenedor se detiene → breve tiempo de inactividad
  3. El nuevo contenedor se inicia → Healthcheck espera hasta que la aplicación esté lista
  4. Traefik obtiene un nuevo endpoint de backend

Configuración de Healthcheck (en la interfaz de Coolify o directamente en la base de datos):

docker exec coolify-db psql -U coolify -d coolify -c "
UPDATE applications SET
  health_check_enabled = true,
  health_check_path = '/api/v1/flags',  -- o '/', '/health', '/ping'
  health_check_port = '80',
  health_check_interval = 15,
  health_check_timeout = 5,
  health_check_retries = 3,
  health_check_start_period = 90  -- 90s de tiempo de inicio para aplicaciones pesadas
WHERE uuid = '<APP_UUID>';
"

TABLA DE PRESUPUESTO DE MEMORIA (al 26/06/2026)

GAMEBOY 4GB RAM — RESUMEN DEL PRESUPUESTO
════════════════════════════════════════════════════════════
Sistema/SO:              400MB (fijo)
Stack Coolify:          ~950MB (coolify+db+redis+sentinel+proxy)
────────────────────────────────────────────────────────────
DISPONIBLE PARA PROYECTOS: ~2650MB
════════════════════════════════════════════════════════════
app-workflow:           límite 1536MB / reserva 768MB
app-billing:            límite 512MB / reserva 256MB
app-landing:            límite 256MB / reserva 128MB
────────────────────────────────────────────────────────────
AÚN LIBRE:             ~346MB de margen de límite
════════════════════════════════════════════════════════════

ADVERTENCIA: El Gameboy está a ~91% de su capacidad de límite.
Un proyecto nuevo > 300MB necesita reequilibrio (reducir
un proyecto existente u optimizar el stack de Coolify).

Si un proyecto nuevo necesita más de 200MB → primero verificar si landing (256MB) puede reducirse a 128MB (sitio estático, necesita poca RAM).


LISTA DE VERIFICACIÓN PARA CADA DESPLIEGUE (Referencia rápida)

ANTES DEL DESPLIEGUE:
  [ ] RAM libre > 800MB
  [ ] Disco > 5GB libre
  [ ] Límites de BD Coolify establecidos (¡no 0!)
  [ ] NODE_OPTIONS en .env correcto (sin errores de salto de línea)
  [ ] Limitación de tasa configurada para endpoints públicos

DESPUÉS DEL DESPLIEGUE:
  [ ] Contenedor en ejecución (docker ps → Up X segundos (healthy))
  [ ] URL accesible (HTTP 2xx/3xx)
  [ ] NODE_OPTIONS activo (docker exec <c> printenv NODE_OPTIONS)
  [ ] Memoria estable (docker stats → no al límite)
  [ ] Sin OOM-Kill (dmesg | grep -i 'oom')

Skills relacionados