🎮 Gameboy-Gate — Exame de Admissão para Cada Projeto
OBRIGATÓRIO: Todo projeto implantado no Gameboy (
<GAMEBOY_IP>) deve passar por este Gate — independente de quem usa Claude, independente de qual projeto. Nenhum lançamento sem PASS.
🔒 Dados de Conexão Local: Os valores reais para
<GAMEBOY_IP>,<GAMEBOY_SSH>,<COOLIFY_URL>,<WATCHDOG_*>estão no arquivo gitignoredgameboy-gate/gameboy.local.md(NÃO no repositório público). Antes de cada execução do Gate, leia este arquivo e substitua os placeholders.
CONTEXTO: O Gameboy
Servidor: Hetzner CAX11 (ARM)
IP: <GAMEBOY_IP> # real: veja gameboy.local.md
SSH: <GAMEBOY_SSH> # real: veja gameboy.local.md
RAM: 4GB total
CPU: 2x vCPU (ARM Neoverse-N1 @ 2.0GHz)
Disco: 40GB SSD
SO: Ubuntu (Docker + Coolify)
Coolify UI: <COOLIFY_URL> # real: veja gameboy.local.md
Reserva Fixa de Overhead (NUNCA reduzir):
SO + Kernel: 400MB
Coolify (incl. DB, Redis, Proxy, Sentinel): 900MB
Buffer de Segurança: 200MB
──────────────────────────────
LIVRE para Projetos: ~2500MB (de 4096MB total)
Projetos já em execução (a partir de 26/06/2026 — nomes reais veja gameboy.local.md):
app-workflow(Activepieces): 1536MB limite / 768MB reservaapp-billing(Next.js): 512MB limite / 256MB reservaapp-landing(Estático): 256MB limite / 128MB reserva- Ocupado: 2304MB limite / 1152MB reserva
FLUXO DO GATE (nesta ordem, sem pular)
Fase G0: Obter Snapshot do Servidor
Sempre primeiro — esta é a verdade sobre o estado atual do 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
"
Daí derivar:
TOTAL_RAM_MB(quase sempre 3840MB = 3.7GB como bytes)COOLIFY_OVERHEAD_MB= soma de todos os contêineres do Coolify (coolify, coolify-db, coolify-redis, coolify-sentinel, coolify-proxy) atualmenteRUNNING_PROJECTS_MB= soma dos mem_limit de todos os contêineres de projeto em execuçãoAVAILABLE_MB= TOTAL_RAM_MB - 400 (SO) - COOLIFY_OVERHEAD_MB - RUNNING_PROJECTS_MB - 200 (segurança)
Fase G1: Cálculo de Limite (orientado por IA, sem valores fixos)
Com base no tipo de projeto, a IA calcula os limites corretos:
IDENTIFICAR TIPO DE PROJETO:
- Node.js / Next.js App → BASE_MB = 350, WORKER_OVERHEAD = 50 por Worker
- Python / Django / FastAPI → BASE_MB = 200, WORKER_OVERHEAD = 30 por Worker
- Go / Rust Service → BASE_MB = 80, WORKER_OVERHEAD = 20 por Worker
- Site Estático (nginx/caddy) → BASE_MB = 64, WORKER_OVERHEAD = 0
- Postgres → BASE_MB = 128, WORKER_OVERHEAD = 10 por Conexão
- Redis → BASE_MB = 64, WORKER_OVERHEAD = 0
- Worker em Background (filas) → BASE_MB = 200, WORKER_OVERHEAD = 50 por Worker
- Serviço de IA/LLM (local) → BASE_MB = 800+ (pelo menos 2GB se modelo local)
Fórmulas de Limite:
MEM_LIMIT_MB = min(AVAILABLE_MB * 0.8, BASE_MB * 3) -- 80% do Disponível, máx 3x Base
MEM_RESERVATION = MEM_LIMIT_MB * 0.5 -- 50% como mínimo garantido
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 à parcela de RAM
NODE_OPTIONS_MB = floor(MEM_LIMIT_MB * 0.70) -- V8 Heap = 70% do limite do contêiner
UV_THREADPOOL_SIZE = min(8, cpu_count * 2) -- Threads libuv
Verificações de Validação ANTES de definir:
-
MEM_LIMIT_MB >= 128(Mínimo para iniciar) -
AVAILABLE_MB - MEM_LIMIT_MB >= 200(Buffer de segurança permanece) - Soma de todos os mem_limits ≤ 3500MB (sem overcommit acima de 85% da RAM física)
- Se a validação falhar → BLOQUEADO, informar Cem
Fase G2: Definir Limites Permanentemente no Banco de Dados do Coolify
Esta é a correção crítica contra regressões de deploy. O Coolify regenera o docker-compose.yaml a cada deploy a partir de seu próprio banco de dados. Somente se os limites estiverem no banco de dados, eles sobreviverão aos deploys.
Se o UUID do aplicativo for conhecido:
<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>';
\"
"
Obter UUID da URL do Coolify: <COOLIFY_URL>/project/.../application/<UUID>/...
Se UUID desconhecido → pesquisar via DB:
docker exec coolify-db psql -U coolify -d coolify -c "SELECT uuid, name FROM applications ORDER BY created_at DESC LIMIT 10;"
Fase G3: Definir Variáveis de Ambiente
No .env do projeto (via echo >> .env ou Coolify UI):
# Para Node.js/Next.js:
NODE_OPTIONS=--max-old-space-size=<NODE_OPTIONS_MB>
UV_THREADPOOL_SIZE=<UV_THREADPOOL_SIZE>
# Para Aplicações de Workflow (Activepieces etc.):
AP_MAX_CONCURRENT_JOBS=5 # previne explosão de sandbox
AP_TRIGGER_DEFAULT_POLL_INTERVAL=15 # reduz carga de linha de base do DB
# Para todas as aplicações:
# (sem logging DEBUG em produção!)
NODE_ENV=production
IMPORTANTE: Sem bug de quebra de linha! Verificar cada variável em sua própria linha:
grep -c '^[A-Z_].*=' .env # deve ser igual ao número de variáveis
# Nenhuma linha deve conter dois '=' exceto em valores
awk -F= 'NF>2 && $1 ~ /^[A-Z_]/' .env && echo "BUG: '=' duplicado na linha!"
Fase G4: Ativar Rate Limiting do Traefik
Para CADA novo serviço 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 'Configuração de rate-limit escrita'
"
O Traefik capta isso automaticamente (watch=true) — nenhum restart necessário.
Em seguida, adicionar o middleware ao roteador do aplicativo (na configuração do Traefik específica do aplicativo):
middlewares:
- rate-limit-strict
Fase G5: Definir Limites de Descritores de Arquivo
O daemon do Docker precisa conhecer os limites. Uma vez por configuração do Gameboy, permanece persistente:
<GAMEBOY_SSH> "
# Verificar se já está definido
grep -q 'nofile' /etc/docker/daemon.json && echo 'already set' || {
# Backup e Atualização
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 atualizado')
\"
# ATENÇÃO: systemctl restart docker reinicia todos os contêineres!
# Somente se não houver tráfego ativo → alinhar com Cem
echo 'NECESSITA: systemctl restart docker (pequena indisponibilidade!)'
}
"
Fase G6: Teste de Carga (o verdadeiro exame de admissão)
50 usuários concorrentes, 60 segundos:
# Teste 1: Acessibilidade Básica
curl -o /dev/null -s -w "HTTP=%{http_code} %{time_total}s\n" https://<APP_URL>/
# Teste 2: 50 requisições HTTP concorrentes (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%"
# Teste 3: Monitorar Memória Durante 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: Critérios de PASS/FAIL
PASS (todos devem ser atendidos):
| Critério | Limite |
|---|---|
| Status HTTP | 2xx ou 3xx (sem 502/503/504) |
| Latência p99 | < 3000ms |
| Crescimento de Memória | estável (< +20% entre t=0 e t=60s) |
| Memória permanece abaixo do limite | < 90% do limite do contêiner |
| Nenhuma requisição com falha | < 0.1% |
| Servidor acessível após teste | HTTP 200/302 em todas as URLs |
| CPU após teste | < 80% sustentada |
FAIL (um é suficiente para BLOQUEADO):
- Algum contêiner está inativo/reiniciado após o teste
- Memória atingiu o limite (> 95%)
Failed requests > 1%p99 > 5000ms- Watchdog interveio durante o teste (verificar:
<WATCHDOG_LOG>)
Fase G8: Resultado do Gate
═══════════════════════════════════════════════════════
🎮 RESULTADO DO GAMEBOY-GATE
═══════════════════════════════════════════════════════
Projeto: [Nome]
App-UUID: [UUID do Coolify]
Hora do Teste: [Data/Hora]
CÁLCULO DE LIMITE:
RAM do Servidor: 3840MB
Overhead do Coolify: [n]MB (medido)
Outros Projetos: [n]MB (soma dos limites)
Disponível: [n]MB
Alocado: MEM=[n]m, CPU=[n], NODE_OPTIONS=[n]MB
CHECKS:
✅/❌ Limites do DB do Coolify definidos persistentemente
✅/❌ NODE_OPTIONS correto (sem bug de nova linha)
✅/❌ Rate-Limiting ativo
✅/❌ Limite de Descritor de Arquivo 65536
✅/❌ Política maxmemory do Redis definida (se Redis)
✅/❌ Teste de Carga 50c/30s aprovado
TESTE DE CARGA:
Req/s: [n] (PASS se > 10)
p50: [n]ms
p99: [n]ms (PASS se < 3000ms)
Falhas: [n]% (PASS se < 0.1%)
Pico de Memória: [n]MB (PASS se < 90% do limite)
═══════════════════════════════════════════════════════
VEREDITO: ✅ PASS — LANÇAMENTO PERMITIDO
❌ FAIL — LANÇAMENTO BLOQUEADO ([Motivo])
═══════════════════════════════════════════════════════
MOTIVOS COMUNS DE FALHA E CORREÇÕES
FAIL: NODE_OPTIONS não definido
# Verificar:
docker exec <container> printenv NODE_OPTIONS
# Correção:
grep -n 'NODE_OPTIONS' .env # verificar bug de nova linha
# Bug: HOST=0.0.0.0NODE_OPTIONS=... → Correção:
sed -i 's/HOST=0.0.0.0NODE_OPTIONS=/HOST=0.0.0.0\nNODE_OPTIONS=/' .env
FAIL: Contêiner com OOM após teste
Causa raiz quase sempre uma das seguintes:
NODE_OPTIONSnão definido → V8 aloca toda a RAM do contêiner- Processos Sandbox (AP, Execução de Código) sem limite de concorrência
- Vazamento de memória no código do aplicativo (menos comum)
# Verificar se houve reinicialização:
docker inspect <container> --format '{{.RestartCount}}'
# Verificar se houve OOM-Kill:
dmesg | grep -i 'oom\|killed' | tail -10
FAIL: p99 > 3000ms
- Endpoint de API pesado sem cache → Implementar cache Redis
- Pool de Conexões PG esgotado → Aumentar
MAX_CONCURRENT_REQUESTSou tamanho do pool - Overhead de Forward-Auth por requisição → Implementar cache JWT
FAIL: Requisições com Falha > 0.1%
- Timeout do Traefik menor que a resposta do aplicativo → Aumentar timeouts do Traefik
- Limite de FD esgotado → Passo G5 (ulimits)
max_connectionsdo PG esgotado → Definirmax_connections=200no PG
RESILIÊNCIA DE DEPLOY: O Protocolo de Sobrevivência do Deploy
Cada deploy do Coolify deve garantir o seguinte para que o servidor não quebre:
Antes de cada deploy verificar:
<GAMEBOY_SSH> "
# RAM livre deve ser > 800MB
FREE=\$(free -m | awk '/^Mem:/{print \$7}')
echo "Disponível: \${FREE}MB"
[ \$FREE -lt 800 ] && echo 'AVISO: RAM insuficiente para deploy!' || echo 'RAM OK para deploy'
# Disco deve ter > 5GB livres (pull de imagem precisa de espaço)
DISK=\$(df / | awk 'NR==2{print \$4}')
echo "Disco livre: \$((DISK/1024))MB"
[ \$DISK -lt 5242880 ] && echo 'AVISO: Disco insuficiente!' || echo 'Disco OK'
"
O que o Coolify faz durante o deploy (e o que devemos garantir):
- O Coolify lê seu DB → gera docker-compose.yaml com os limites definidos no DB (G2 → persistente ✅)
- O contêiner para → pequena indisponibilidade
- Novo contêiner inicia → Healthcheck aguarda até o aplicativo estar pronto
- O Traefik recebe novo endpoint de backend
Configuração do Healthcheck (no Coolify UI ou diretamente no DB):
docker exec coolify-db psql -U coolify -d coolify -c "
UPDATE applications SET
health_check_enabled = true,
health_check_path = '/api/v1/flags', -- ou '/', '/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 tempo de inicialização para aplicações pesadas
WHERE uuid = '<APP_UUID>';
"
TABELA DE ORÇAMENTO DE MEMÓRIA (a partir de 26/06/2026)
GAMEBOY 4GB RAM — RESUMO DO ORÇAMENTO
════════════════════════════════════════════════════════════
Sistema/SO: 400MB (fixo)
Stack Coolify: ~950MB (coolify+db+redis+sentinel+proxy)
────────────────────────────────────────────────────────────
DISPONÍVEL PARA PROJETOS: ~2650MB
════════════════════════════════════════════════════════════
app-workflow: 1536MB limite / 768MB reserva
app-billing: 512MB limite / 256MB reserva
app-landing: 256MB limite / 128MB reserva
────────────────────────────────────────────────────────────
AINDA LIVRE: ~346MB de margem de limite
════════════════════════════════════════════════════════════
AVISO: O Gameboy está cerca de 91% de sua capacidade de limite.
Novo projeto > 300MB precisa de rebalanceamento (reduzir
projeto existente ou otimizar stack Coolify).
Se um novo projeto precisar de mais de 200MB → primeiro verificar se o landing (256MB) pode ser reduzido para 128MB (site estático, precisa de pouca RAM).
CHECKLIST PARA CADA DEPLOY (Referência Rápida)
ANTES DO DEPLOY:
[ ] RAM livre > 800MB
[ ] Disco > 5GB livre
[ ] Limites do DB do Coolify definidos (não 0!)
[ ] NODE_OPTIONS no .env correto (sem bugs de quebra de linha)
[ ] Rate-Limiting configurado para endpoints públicos
APÓS DEPLOY:
[ ] Contêiner em execução (docker ps → Up X seconds (healthy))
[ ] URL acessível (HTTP 2xx/3xx)
[ ] NODE_OPTIONS ativo (docker exec <c> printenv NODE_OPTIONS)
[ ] Memória estável (docker stats → não no limite)
[ ] Sem OOM-Kill (dmesg | grep -i 'oom')
[ ] CPU sustentada < 80%
NOTAS ADICIONAIS
- Este skill é parte do conjunto pessoal de skills do Claude Code de Cem-Tas96, mantido em
~/.claude/skills/. - O arquivo
gameboy.local.mdcontém dados sensíveis de conexão e não deve ser versionado. - Qualquer modificação nas fases do Gate deve ser aprovada por Cem.