Gameboy-Gate — 全プロジェクトの入学試験

すべてのプロジェクトがGameboyサーバーにデプロイされる前に通過しなければならないゲート(入学試験)です。リソース制限の計算、Coolify DBへの永続設定、ロードテストを自動化し、安定したリリースを保証します。

Gameboy-Gate — 全プロジェクトの入学試験 とは?

このスキルは、Claude CodeがGameboyサーバー(Hetzner CAX11、RAM 4GB)にデプロイするすべてのプロジェクトに対して、厳格な入学試験(ゲート)を自動的に適用するためのものです。主な機能として、サーバースナップショットの取得(G0)、プロジェクトタイプに基づく動的なメモリ/CPU制限の計算(G1)、Coolifyデータベースへの制限の永続設定(G2)、環境変数の設定(G3)、Traefikレート制限の有効化(G4)、ファイルディスクリプタ制限の設定(G5)、負荷テストの実行(G6、50同時接続・60秒)など、8つのフェーズを順に実行します。すべてのフェーズをパスした場合のみリリースが許可され、特定の条件(p99レイテンシ3000ms未満、メモリ使用率が制限の90%未満、失敗リクエスト0.1%未満など)を満たす必要があります。このスキルは、macOSとWindowsの両方で、`~/.claude/skills/`ディレクトリに保存され、複数のマシン間で同期される個人用スキルであり、Gameboyサーバー上のリソースコンフリクトやデプロイ後のパフォーマンス劣化を防止するために設計されています。

対応✓Claude Code~Codex CLI~Cursor
npx skills add Cem-Tas96/claude-skills

Installed? Explore more 生産性&コラボレーション skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

🎮 Gameboy-Gate — 全プロジェクトの入学試験

必須: Gameboy(<GAMEBOY_IP>)にデプロイされるすべてのプロジェクトは、このゲートを通過する必要があります。Claudeを使用するユーザーやプロジェクトの種類を問いません。PASSなしにリリースはできません。

🔒 接続情報はローカル: <GAMEBOY_IP>、<GAMEBOY_SSH>、<COOLIFY_URL>、<WATCHDOG_*> の実際の値は、gitignore されているファイル gameboy-gate/gameboy.local.md に記載されています(公開リポジトリには含まれません)。各ゲート実行前にこのファイルを読み込み、プレースホルダを置き換えてください。

コンテキスト: Gameboy

サーバー: Hetzner CAX11 (ARM)
IP:      <GAMEBOY_IP>          # 実際: gameboy.local.md を参照
SSH:     <GAMEBOY_SSH>         # 実際: gameboy.local.md を参照
RAM:     4GB 合計
CPU:     2x vCPU (ARM Neoverse-N1 @ 2.0GHz)
ディスク: 40GB SSD
OS:      Ubuntu (Docker + Coolify)
Coolify UI: <COOLIFY_URL>     # 実際: gameboy.local.md を参照

固定オーバーヘッド予約(絶対に下回らないこと):

OS + カーネル:              400MB
Coolify (DB、Redis、Proxy、Sentinelを含む): 900MB
セーフティバッファ:         200MB
──────────────────────────────
プロジェクトに利用可能:    ~2500MB (4096MB 合計から)

現在実行中のプロジェクト(2026年6月26日現在 — 実際の名前は gameboy.local.md を参照):

  • app-workflow (Activepieces): 1536MB 制限 / 768MB 予約
  • app-billing (Next.js): 512MB 制限 / 256MB 予約
  • app-landing (Static): 256MB 制限 / 128MB 予約
  • 使用中: 2304MB 制限 / 1152MB 予約

ゲートフロー(この順序で実行、スキップ不可)

フェーズ G0: サーバースナップショットの取得

常に最初に実行 — これがサーバーの現在の状態に関する真実です:

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

ここから以下を導出:

  • TOTAL_RAM_MB (ほとんどの場合 3840MB = 3.7GB をバイト単位で)
  • COOLIFY_OVERHEAD_MB = 現在のすべてのCoolifyコンテナ(coolify、coolify-db、coolify-redis、coolify-sentinel、coolify-proxy)の合計
  • RUNNING_PROJECTS_MB = 実行中のすべてのプロジェクトコンテナの mem_limit の合計
  • AVAILABLE_MB = TOTAL_RAM_MB - 400 (OS) - COOLIFY_OVERHEAD_MB - RUNNING_PROJECTS_MB - 200 (セーフティ)

フェーズ G1: 制限計算(AI主導、ハードコードなし)

プロジェクトタイプに基づいて、AIが適切な制限を計算します:

プロジェクトタイプの識別:
- Node.js / Next.js アプリ       → BASE_MB = 350, WORKER_OVERHEAD = ワーカーあたり 50
- Python / Django / FastAPI   → BASE_MB = 200, WORKER_OVERHEAD = ワーカーあたり 30
- Go / Rust サービス           → BASE_MB = 80,  WORKER_OVERHEAD = ワーカーあたり 20
- 静的サイト (nginx/caddy)   → BASE_MB = 64,  WORKER_OVERHEAD = 0
- Postgres                    → BASE_MB = 128, WORKER_OVERHEAD = 接続あたり 10
- Redis                       → BASE_MB = 64,  WORKER_OVERHEAD = 0
- バックグラウンドワーカー (キュー)  → BASE_MB = 200, WORKER_OVERHEAD = ワーカーあたり 50
- AI/LLM サービス (ローカル)      → BASE_MB = 800+ (モデルがローカルの場合は最低 2GB)

制限の計算式:

MEM_LIMIT_MB    = min(AVAILABLE_MB * 0.8, BASE_MB * 3)   -- 利用可能量の80%、最大ベースの3倍
MEM_RESERVATION = MEM_LIMIT_MB * 0.5                      -- 50%を保証最小値として
MEM_SWAP_MB     = MEM_LIMIT_MB * 1.4                      -- 40%のスワップオーバーヘッドを許可
CPU_LIMIT       = round(2.0 * (MEM_LIMIT_MB / AVAILABLE_MB), 1)  -- RAM割合に比例

NODE_OPTIONS_MB = floor(MEM_LIMIT_MB * 0.70)              -- V8ヒープ = コンテナ制限の70%
UV_THREADPOOL_SIZE = min(8, cpu_count * 2)                -- libuvスレッド

設定前の検証チェック:

  • MEM_LIMIT_MB >= 128 (起動するための最小値)
  • AVAILABLE_MB - MEM_LIMIT_MB >= 200 (セーフティバッファが残っている)
  • すべての mem_limit の合計 ≤ 3500MB (物理RAMの85%を超えるオーバーコミットなし)
  • 検証に失敗した場合 → ブロック、Cemに通知

フェーズ G2: Coolify DBに制限を永続的に設定

これはデプロイ後退行を防ぐための重要な修正です。 Coolifyはデプロイのたびに自身のDBから docker-compose.yaml を再生成します。制限がDBに設定されている場合にのみ、デプロイ後も持続します。

アプリのUUIDが既知の場合:

<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はCoolify URLから取得: <COOLIFY_URL>/project/.../application/<UUID>/...

UUIDが不明な場合 → DBで検索:

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

フェーズ G3: 環境変数の設定

プロジェクトの .env に設定(echo >> .env または Coolify UI 経由):

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

# ワークフローアプリ (Activepieces など) の場合:
AP_MAX_CONCURRENT_JOBS=5     # サンドボックス爆発を防止
AP_TRIGGER_DEFAULT_POLL_INTERVAL=15  # DBベースライン負荷を低減

# すべてのアプリで:
# (本番環境ではDEBUGログ出力なし!)
NODE_ENV=production

重要: 改行バグに注意! 各変数が個別の行にあることを確認:

grep -c '^[A-Z_].*=' .env  # 変数の数と一致する必要あり
# 値に含まれる場合を除き、1行に2つの '=' があってはならない
awk -F= 'NF>2 && $1 ~ /^[A-Z_]/' .env && echo "バグ: 行内に複数の = があります!"

フェーズ G4: Traefikレート制限の有効化

すべての新しい公開サービスに対して:

<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 'レート制限設定を書き込みました'
"

Traefikはこれを自動的に取得します (watch=true) — 再起動は不要です。

次に、ミドルウェアをアプリルーターに追加します(アプリ固有のTraefik設定内):

middlewares:
  - rate-limit-strict

フェーズ G5: ファイルディスクリプタ制限の設定

Dockerデーモンは制限を認識している必要があります。Gameboyセットアップごとに1回、永続的に保持されます:

<GAMEBOY_SSH> "
# 既に設定されているか確認
grep -q 'nofile' /etc/docker/daemon.json && echo 'すでに設定されています' || {
  # バックアップと更新
  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 を更新しました')
  \"
  # 注意: systemctl restart docker はすべてのコンテナを再起動します!
  # アクティブなトラフィックがない場合のみ → Cemと調整
  echo '必要: systemctl restart docker (短いダウンタイム!)'
}
"

フェーズ G6: 負荷テスト(本来の入学試験)

50の同時接続ユーザー、60秒:

# テスト1: 基本到達可能性
curl -o /dev/null -s -w "HTTP=%{http_code} %{time_total}s\n" https://<APP_URL>/

# テスト2: 50の同時HTTPリクエスト (30秒)
/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%"

# テスト3: 負荷中のメモリ監視
<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

フェーズ G7: PASS/FAIL 基準

PASS(すべて満たす必要あり):

基準しきい値
HTTPステータス2xx または 3xx (502/503/504 不可)
p99レイテンシ< 3000ms
メモリ増加安定 (< t=0 から t=60s の間で +20% 未満)
メモリが制限未満< コンテナ制限の 90%
失敗リクエストなし< 0.1%
テスト後もサーバーにアクセス可能すべてのURLで HTTP 200/302
テスト後のCPU< 80% 持続

FAIL(1つでブロック):

  • テスト後にダウン/再起動したコンテナがある
  • メモリが制限に達した (> 95%)
  • Failed requests > 1%
  • p99 > 5000ms
  • テスト中にWatchdogが介入した(確認: <WATCHDOG_LOG>)

フェーズ G8: ゲート結果

═══════════════════════════════════════════════════════
🎮 GAMEBOY-GATE 結果
═══════════════════════════════════════════════════════
プロジェクト:  [名前]
App-UUID:    [Coolify UUID]
テスト日時:   [日付/時刻]

制限計算:
  サーバーRAM:              3840MB
  Coolifyオーバーヘッド:    [n]MB (測定値)
  他のプロジェクト:         [n]MB (制限の合計)
  利用可能:                 [n]MB
  割り当て:                 MEM=[n]m, CPU=[n], NODE_OPTIONS=[n]MB

チェック:
  ✅/❌ Coolify DB 制限が永続的に設定されている
  ✅/❌ NODE_OPTIONS が正しい (改行バグなし)
  ✅/❌ レート制限が有効
  ✅/❌ ファイルディスクリプタ制限 65536
  ✅/❌ Redis maxmemory ポリシーが設定されている (Redisの場合)
  ✅/❌ 負荷テスト 50c/30s をパス

負荷テスト:
  Req/s:    [n]     (PASS: > 10)
  p50:      [n]ms
  p99:      [n]ms   (PASS: < 3000ms)
  失敗:     [n]%    (PASS: < 0.1%)
  メモリピーク: [n]MB   (PASS: < 制限の 90%)

═══════════════════════════════════════════════════════
判定: ✅ PASS — リリース許可
      ❌ FAIL — リリースブロック ([理由])
═══════════════════════════════════════════════════════

よくあるFAILの理由と修正

FAIL: NODE_OPTIONS が設定されていない

# 確認:
docker exec <container> printenv NODE_OPTIONS
# 修正:
grep -n 'NODE_OPTIONS' .env  # 改行バグを確認
# バグ: HOST=0.0.0.0NODE_OPTIONS=... → 修正:
sed -i 's/HOST=0.0.0.0NODE_OPTIONS=/HOST=0.0.0.0\nNODE_OPTIONS=/' .env

FAIL: テスト後にコンテナがOOM

根本原因はほとんどの場合以下のいずれか:

  1. NODE_OPTIONS が設定されていない → V8がコンテナRAMを完全に消費
  2. サンドボックスプロセス (AP、コード実行) に同時実行制限がない
  3. アプリコードのメモリリーク (まれ)
# 再起動が発生したか確認:
docker inspect <container> --format '{{.RestartCount}}'
# OOMキルが発生したか確認:
dmesg | grep -i 'oom\|killed' | tail -10

FAIL: p99 > 3000ms

  1. キャッシュなしのヘビーAPIエンドポイント → Redisキャッシュを実装
  2. PG接続プールが枯渇 → MAX_CONCURRENT_REQUESTS またはプールサイズを増加
  3. リクエストごとのForward-Authオーバーヘッド → JWTキャッシュを実装

FAIL: 失敗リクエスト > 0.1%

  1. Traefikタイムアウトがアプリ応答より短い → Traefikタイムアウトを増加
  2. FD制限が枯渇 → ステップG5 (ulimits)
  3. PG max_connections が枯渇 → PGで max_connections=200 を設定

デプロイ耐性: デプロイ生存プロトコル

すべてのCoolifyデプロイは、サーバーがクラッシュしないように以下を保証する必要があります:

各デプロイ前に確認:

<GAMEBOY_SSH> "
# 空きRAMが800MB以上必要
FREE=\$(free -m | awk '/^Mem:/{print \$7}')
echo "利用可能: \${FREE}MB"
[ \$FREE -lt 800 ] && echo '警告: デプロイにRAMが不足しています!' || echo 'デプロイにRAMは十分です'
# ディスク空き容量が5GB以上必要 (イメージプルにスペースが必要)
DISK=\$(df / | awk 'NR==2{print \$4}')
echo "ディスク空き: \$((DISK/1024))MB"
[ \$DISK -lt 5242880 ] && echo '警告: ディスクが不足しています!' || echo 'ディスクは十分です'
"

Coolifyがデプロイ時に行うこと(そして私たちが保証しなければならないこと):

  1. Coolifyは自身のDBを読み取る → DBに設定された制限を含めて docker-compose.yaml を生成 (G2 → 永続的 ✅)
  2. コンテナ停止 → 短いダウンタイム
  3. 新しいコンテナ起動 → Healthcheckはアプリの準備ができるまで待機
  4. Traefikは新しいバックエンドエンドポイントを取得

Healthcheck設定 (Coolify UI または直接DB):

docker exec coolify-db psql -U coolify -d coolify -c "
UPDATE applications SET
  health_check_enabled = true,
  health_check_path = '/api/v1/flags',  -- または '/', '/health', '/ping'
  health_check_port = '80',
  health_check_interval = 15,
  health_check_timeout = 5,
  health_check_retries = 3,
  health_check_start_period = 90  -- ヘビーアプリの起動時間90秒
WHERE uuid = '<APP_UUID>';
"

メモリバジェット表 (2026年6月26日現在)

GAMEBOY 4GB RAM — 予算概要
════════════════════════════════════════════════════════════
システム/OS:              400MB (固定)
Coolifyスタック:          ~950MB (coolify+db+redis+sentinel+proxy)
────────────────────────────────────────────────────────────
プロジェクトに利用可能: ~2650MB
════════════════════════════════════════════════════════════
app-workflow:          1536MB 制限 / 768MB 予約
app-billing:            512MB 制限 / 256MB 予約
app-landing:            256MB 制限 / 128MB 予約
────────────────────────────────────────────────────────────
残り空き:             ~346MB 制限の余裕
════════════════════════════════════════════════════════════

警告: Gameboyは制限容量の約91%が使用中です。
300MBを超える新しいプロジェクトにはリバランスが必要です
(既存プロジェクトの縮小またはCoolifyスタックの最適化)。

新しいプロジェクトが200MB以上必要な場合 → まずlanding (256MB) を128MBに減らせるか確認(静的サイトでRAMはほとんど不要)。


各デプロイのチェックリスト (クイックリファレンス)

デプロイ前:
  [ ] 空きRAM > 800MB
  [ ] ディスク > 5GB 空き
  [ ] Coolify DB 制限が設定されている (0 ではない!)
  [ ] .env の NODE_OPTIONS が正しい (改行バグなし)
  [ ] 公開エンドポイントにレート制限が設定されている

デプロイ後:
  [ ] コンテナが実行中 (docker ps → Up X seconds (healthy))
  [ ] URL にアクセス可能 (HTTP 2xx/3xx)
  [ ] NODE_OPTIONS が有効 (docker exec <c> printenv NODE_OPTIONS)
  [ ] メモリが安定 (docker stats → 制限に達していない)
  [ ] OOMキルなし (dmesg | grep -i 'oom\|killed')
  [ ] 負荷テスト完了 (50c/60s)

最終ルール: 疑わしい場合は、常にゲートを実行してください。Gameboyにリソースが不足しているよりも、デプロイに少し時間がかかる方がはるかに良いです。

関連スキル