agentic-microservice-deployer について
ローカルの自動化コードをCoolify上の内部マイクロサービスにし、Dockerネットワーク経由でn8nから利用する手順をAgentに示すスキルです。リポジトリ準備からデプロイ、テスト、エンドポイント文書の作成まで扱います。
主な機能
- 01実データのテスト入力を収集
- 02GitHubリポジトリの作成と設定
- 03Coolifyへの内部サービスのデプロイ
- 04デプロイ監視とテスト実行
- 05n8n向けエンドポイント文書の生成
活用シーン
- 01ローカル自動化を内部APIにまとめる
- 02n8nワークフロー向けサービスのデプロイ
- 03エンドポイントの検証と呼び出し方法の文書化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ kevinrivm/ agentic-microservice-deployer --skill agentic-microservice-deployerインストール後、Claude Code で/agentic-microservice-deployerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.md.gitattributesexecution/coolify_manager.pygithub_manager.py詳細
エージェント型マイクロサービスデプロイヤー
ローカルの自動化コードを、安全な内部マイクロサービスに変換して Coolify にデプロイし、 Docker 内部ネットワーク経由で n8n からのみアクセスできるようにします。公開 URL は不要です。手間もかかりません。
⚙️ 初期セットアップ — 始める前にお読みください
このスキルには、プロジェクトの execution/ に 必ず配置する必要がある 2 つの実行スクリプトが含まれています。
存在しない場合は、スキルのフォルダーからコピーして作成してください。
import shutil, os
# Ruta donde está instalado el skill (ajustar si es diferente)
SKILL_DIR = ".agent/skills/agentic-microservice-deployer/execution"
os.makedirs("execution", exist_ok=True)
for script in ["coolify_manager.py", "github_manager.py"]:
src = os.path.join(SKILL_DIR, script)
dst = os.path.join("execution", script)
if not os.path.exists(dst):
if os.path.exists(src):
shutil.copy(src, dst)
print(f"Copiado: {script}")
else:
print(f"ADVERTENCIA: {src} no encontrado. Descarga manual necesaria.")
else:
print(f"Ya existe: execution/{script}")
依存関係がインストールされていない場合は、インストールしてください。
pip install requests python-dotenv
スクリプトがスキルのフォルダーにない場合は、リポジトリから取得できます。
https://github.com/kevinrivm/agentic-microservice-deployer/tree/main/execution
手動で行う作業と自動で行う作業
| 手順 | 担当 | 頻度 |
|---|---|---|
| Coolify UI で GitHub App を作成 | 👤 ユーザー | VPS ごとに 1 回 |
| それ以外すべて | 🤖 エージェント | プロジェクトごと |
手動で行う唯一の手順では、ブラウザーが必要です。GitHub では、ユーザーが「Install」をクリックする必要があるためです (GitHub のセキュリティ上意図された OAuth ハンドシェイク)。これに代わる API はありません。
ステップ 0:テストデータを収集する — 構築前に必須
なぜ最初に行うのですか? エージェントが次の目的で、実際のテストデータを必要とするためです。
- 入力の正確な形式を把握する(誤ったコードにつながる思い込みを避けます)
- ユーザーに引き渡す前にサービスを自動テストする
- 最初から境界ケースを検証する
少なくとも 1 つのテスト例を用意するまでは、コードを書き始めないでください。
続行する前に、必ずユーザーに尋ねてください。
マイクロサービスを構築する前に、実際のテストデータが必要です。 これにより、次のことが可能になります。
- データの実際の形式に基づいてコードを構築する
- 引き渡す前にサービスを自動テストする
- ユーザーが見つける前にエラーを検出する
次の情報を提供してください。
- サービスが受け取る各タイプの入力の実例 (例:OCR 処理するチケットの例、メール全文、画像、サンプル JSON)
- その入力に対する期待される応答 (例:「このチケットから日付、合計金額、仕入先を抽出してほしい」)
- もしあれば、境界ケースや難しいケース (例:「チケットがぼやけていたり、英語だったりすることがある」)
現時点で実際のデータを用意できない場合は、説明に基づいて合成データを生成できますが、 実際のデータを使うほうが良い結果につながります。
テストデータを受け取ったら、次のようにしてください。
- 開発中に参照できるよう、
.tmp/test_data/に保存する - Pydantic モデルとサービスロジックの構築に使用する
- 同じデータを、引き渡し前の ステップ 5(自動テスト) でも使用する
前提条件 — 開始前に確認
環境変数(.env 内):
GITHUB_TOKEN= # PAT con scope 'repo' — crear repos privados + vincular GitHub Apps
COOLIFY_URL= # ej: https://panel-coolify.tudominio.com
COOLIFY_TOKEN= # API token de Coolify (Settings > API Keys)
COOLIFY_PROJECT_UUID= # UUID del proyecto Coolify donde desplegar
不足しているものがあれば、続行する前にユーザーに依頼してください。
Coolify の GitHub App(VPS ごとに 1 回):
ユーザーが Coolify でこの設定をしたことがない場合は、次のように説明してください。
なぜ手動なのですか? GitHub では、ユーザーがブラウザーで「Install」をクリックする必要があるためです (OAuth ハンドシェイク)。これは VPS/クライアントごとに 1 回だけ 実行します。その後、 ユーザーの操作なしで、エージェントが今後のすべてのリポジトリへのアクセスを管理します。
ユーザー向け手順(5 分):
- Coolify パネル → サイドメニューの 「Sources」
- 「Add」 → 「GitHub App」 をクリック
- Name:説明的な名前(例:
coolify-tudominio)- Organization:空欄のままにする
- System Wide:チェックしない
- Webhook Endpoint:Coolify の HTTPS ドメインを選択
- 「Register Now」 をクリック → GitHub のインストール画面が開きます
- GitHub で 「All repositories」 を選択 ← 今後のアクセスをエージェントが管理するために重要です
- 「Install」 をクリック → 自動的に Coolify に戻ります
✅ これで準備完了です。エージェントが今後のすべてのリポジトリを自律的に管理します。
確認できたら、ステップ 1 に進んでください。
ステップ 1:リポジトリの構成を準備する
本番用リポジトリには、コンテナに必要なものだけを含めます。
必須の最小構成:
nombre-del-servicio/
├── main.py # FastAPI con X-API-Key y GET /health
├── execution/ # scripts usados en runtime
├── directives/
│ └── README.md # qué hace el servicio, endpoints, variables
├── Dockerfile
├── requirements.txt
├── .env.example
└── .gitignore
必須の.gitignore:
.env
.env.*
!.env.example
*.json
!package.json
__pycache__/
*.pyc
.tmp/
credentials.json
token.json
標準のDockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
認証機能を備えた最小限のmain.py:
from fastapi import FastAPI, HTTPException, Security
from fastapi.security import APIKeyHeader
import os
app = FastAPI()
API_KEY = os.getenv("SERVICE_API_KEY", "")
api_key_header = APIKeyHeader(name="X-API-Key")
async def verify_key(key: str = Security(api_key_header)):
if key != API_KEY:
raise HTTPException(status_code=403, detail="Forbidden")
return key
@app.get("/health")
def health():
return {"status": "ok"}
# Endpoints del negocio — siempre con dependencies=[Depends(verify_key)]
ステップ 2:GitHub にリポジトリを作成して GitHub App を連携する
execution/github_manager.pyを使用します:
import sys, os, requests
sys.path.insert(0, 'execution')
from github_manager import create_private_repo, grant_github_app_access, initialize_and_push
from dotenv import load_dotenv
load_dotenv()
REPO_NAME = "nombre-del-servicio"
DESCRIPTION = "Microservicio: descripción breve"
# 2a. Crear repo privado
result = create_private_repo(REPO_NAME, DESCRIPTION)
if not result:
raise Exception("Fallo la creacion del repo")
owner, repo_name, repo_id = result
# 2b. Vincular al GitHub App de Coolify (automatiza el paso de "dar acceso al repo")
COOLIFY_URL = os.getenv("COOLIFY_URL")
COOLIFY_TOKEN = os.getenv("COOLIFY_TOKEN")
coolify_h = {"Authorization": f"Bearer {COOLIFY_TOKEN}", "Accept": "application/json"}
github_apps = requests.get(f"{COOLIFY_URL}/api/v1/github-apps", headers=coolify_h).json()
private_apps = [a for a in github_apps if not a.get("is_public")]
installation_id = private_apps[0]["installation_id"]
grant_github_app_access(repo_id, installation_id)
# 2c. Subir el código
initialize_and_push(repo_name, owner)
ステップ 3:Coolify でアプリを作成して設定する
⚠️ 必須のサブステップが 3 つあります。3b を省略すると、サービスがパブリックに公開され、 n8n からネットワーク名でアクセスできなくなります。
3a. アプリを作成する
import sys
sys.path.insert(0, 'execution')
from coolify_manager import CoolifyManager
manager = CoolifyManager()
ALIAS = repo_name # nombre DNS interno — n8n usará: http://ALIAS:8000
# Endpoint correcto: /api/v1/applications/private-github-app
# (NO /api/v1/applications — ese da 404 para repos privados)
app = manager.create_application(repo_name, f"{owner}/{repo_name}")
APP_UUID = app["uuid"]
print(f"App creada: {APP_UUID}")
3b. 内部ネットワークを設定する — 重要、省略しないこと
Coolify は FQDN を自動的に割り当てます。このステップで、サービスを内部サービスとして設定します:
manager.configure_application(APP_UUID, ALIAS)
# Confirma: alias de red configurado, FQDN eliminado, healthcheck activo
重要:
configure_applicationで API 経由の FQDN 削除に失敗した場合 (Coolify の一部のバージョンでは対応していません)、ユーザーに手動で削除するよう伝えてください: パネル → app → Settings → FQDN → 削除 → Save。 FQDN がパブリックでも、サービスには常に内部からアクセスできますが、 セキュリティのため削除してください。
3c. API キー認証を使用するかユーザーに確認する
変数を設定する前に、ユーザーに確認してください:
サービスは内部専用です(Docker ネットワーク経由で n8n からのみアクセス可能)。 ネットワークですでに保護されているため、API キーは任意の追加レイヤーです。
X-API-Key による認証を追加しますか?
- はい → 今後の設定ミスや横方向への侵害に対して、より安全です
- いいえ → よりシンプルになり、n8n は追加のヘッダーなしで呼び出せます
ユーザーが「はい」と答えた場合 — API キーあり:
import requests, os, secrets
from dotenv import load_dotenv
load_dotenv()
SERVICE_API_KEY = secrets.token_hex(32) # NUNCA usar strings hardcodeados
print(f"SERVICE_API_KEY: {SERVICE_API_KEY}")
print("(Guarda este valor — lo usarás en n8n como header X-API-Key)")
COOLIFY_URL = os.getenv("COOLIFY_URL")
headers = {"Authorization": f"Bearer {os.getenv('COOLIFY_TOKEN')}", "Content-Type": "application/json"}
env_vars = [
{"key": "SERVICE_API_KEY", "value": SERVICE_API_KEY},
# Otras vars del servicio:
# {"key": "OPENAI_API_KEY", "value": "sk-..."},
]
for var in env_vars:
requests.post(f"{COOLIFY_URL}/api/v1/applications/{APP_UUID}/envs",
headers=headers, json={**var, "is_preview": False})
print("Variables configuradas")
main.pyで認証ミドルウェアが有効になっていることを確認してください(ステップ 1 を参照)。
ユーザーが「いいえ」と答えた場合 — API キーなし:
# Solo configurar las vars de negocio (sin SERVICE_API_KEY)
env_vars = [
# {"key": "OPENAI_API_KEY", "value": "sk-..."},
]
for var in env_vars:
requests.post(f"{COOLIFY_URL}/api/v1/applications/{APP_UUID}/envs",
headers=headers, json={**var, "is_preview": False})
main.pyで、Security(api_key_header)を削除してエンドポイントを簡素化します:
@app.post("/mi-endpoint")
def mi_endpoint(data: dict): # sin dependencies=[Depends(verify_key)]
...
3d. デプロイする
result = manager.deploy_application(APP_UUID)
deployment_uuid = result["deployments"][0]["deployment_uuid"]
print(f"Deploy iniciado: {deployment_uuid}")
ステップ 4:デプロイを監視する
import time, json, requests, os
from dotenv import load_dotenv
load_dotenv()
headers = {"Authorization": f"Bearer {os.getenv('COOLIFY_TOKEN')}", "Accept": "application/json"}
time.sleep(90)
r = requests.get(f"{os.getenv('COOLIFY_URL')}/api/v1/deployments/{deployment_uuid}", headers=headers)
data = r.json()
status = data.get("status")
logs = json.loads(data.get("logs", "[]"))
print(f"Status: {status}")
for log in logs:
if not log.get("hidden"):
output = log.get("output", "")[:200]
if output.strip():
print(f" [{log.get('type','').upper()}] {output}")
成功を示す項目:
status: finished- ログ:
Healthcheck status: "healthy" - ログ:
Rolling update completed
ステップ 5:ユーザーに引き渡す前に自動テストを実行する
なぜ最初にパブリック FQDN でテストするのですか? エージェントは Docker 内ではなく、ユーザーのマシン(ホスト)で実行されます。 内部 URL
http://alias:8000にアクセスできるのは、coolifyネットワーク内のコンテナだけです。 エージェントが実際の HTTP リクエストでテストするには、一時的なパブリック URL が必要です。
5a. ユーザーにテストデータを依頼する
テストの前に、ユーザーに確認してください:
「引き渡す前にマイクロサービスが正しく動作することを確認するため、 各エンドポイントで使う実際のテストデータが必要です。たとえば:
- 処理するテスト用メールアドレスは何にしますか?
- 送信するサンプルテキスト/ペイロードは何にしますか?
今すぐ実際のデータを用意できない場合は、スキーマに基づいて合成データを生成できます。」
5b. 直前のデプロイで使用したパブリック URL を取得する
import requests, os, time
from dotenv import load_dotenv
load_dotenv()
headers_api = {"Authorization": f"Bearer {os.getenv('COOLIFY_TOKEN')}", "Accept": "application/json"}
app = requests.get(f"{os.getenv('COOLIFY_URL')}/api/v1/applications/{APP_UUID}", headers=headers_api).json()
PUBLIC_URL = app.get("fqdn", "").rstrip("/")
print(f"URL publica para tests: {PUBLIC_URL}")
# ej: http://uuid.187.77.17.72.sslip.io
注: FQDN が有効なのはテスト中のみです。アプリ作成時に Coolify が自動的に割り当てます。 すべてのテストに合格した後、ステップ 5d で削除します。
5c. テストを実行する
import json
AUTH_HEADER = {"X-API-Key": SERVICE_API_KEY} if SERVICE_API_KEY else {}
test_results = []
# Test 1: Healthcheck (siempre)
r = requests.get(f"{PUBLIC_URL}/health", headers=AUTH_HEADER, timeout=10)
passed = r.status_code == 200 and r.json().get("status") == "ok"
test_results.append({"test": "GET /health", "status": r.status_code, "passed": passed})
print(f"{'OK' if passed else 'FAIL'} GET /health → {r.status_code}")
# Test 2+: Endpoints del negocio (usar data de prueba del usuario o sintética)
# Ejemplo — adaptar según los endpoints reales de main.py:
test_payload = {
# poner aqui los datos de prueba que dio el usuario
}
r2 = requests.post(
f"{PUBLIC_URL}/tu-endpoint",
headers={**AUTH_HEADER, "Content-Type": "application/json"},
json=test_payload,
timeout=30
)
passed2 = r2.status_code == 200
test_results.append({"test": "POST /tu-endpoint", "status": r2.status_code,
"passed": passed2, "response": r2.text[:200]})
print(f"{'OK' if passed2 else 'FAIL'} POST /tu-endpoint → {r2.status_code}")
print(f" Response: {r2.text[:200]}")
# Resumen
all_passed = all(t["passed"] for t in test_results)
print(f"\n{'TODOS LOS TESTS PASARON' if all_passed else 'HAY TESTS FALLIDOS'}")
for t in test_results:
print(f" {'OK' if t['passed'] else 'FAIL'} {t['test']}")
テストに失敗した場合:
- デプロイのログを確認し、エラーの原因を把握する
main.pyのコードを修正するgit push+manager.deploy_application(APP_UUID)を実行し、完了を待って再テストする- すべてのテストに合格するまで繰り返す
5d. テスト合格後 → 内部ネットワークに移行する
すべてのテストに合格したら、FQDN を削除し、内部サービスとして設定します:
base = f"{os.getenv('COOLIFY_URL')}/api/v1/applications/{APP_UUID}"
headers_coolify = {"Authorization": f"Bearer {os.getenv('COOLIFY_TOKEN')}",
"Content-Type": "application/json"}
# PATCHes separados (la API de Coolify rechaza campos mezclados)
requests.patch(base, headers=headers_coolify, json={"custom_network_aliases": ALIAS})
requests.patch(base, headers=headers_coolify, json={"domains": ""}) # elimina FQDN publico
requests.patch(base, headers=headers_coolify, json={"dockerfile_location": "/Dockerfile"})
# Redeploy para aplicar cambio de red
manager.deploy_application(APP_UUID)
time.sleep(60)
# Verificar
app_final = requests.get(base, headers=headers_coolify).json()
assert not app_final.get("fqdn"), "FQDN no eliminado"
assert app_final.get("custom_network_aliases") == ALIAS, "Alias no configurado"
print(f"Servicio migrado a red interna. URL para n8n: http://{ALIAS}:8000")
ステップ 6:エンドポイントのドキュメントを生成する(API_DOCS.md)
エージェントはmain.pyを読み取り、curl コマンドと n8n 用の設定をまとめたファイルを生成します。
ユーザーはコードに手を加えず、そのまま HTTP Request ノードにコピーできます。
# Leer main.py para extraer endpoints reales
with open("main.py", "r", encoding="utf-8") as f:
source = f.read()
# El agente lee el codigo y extrae: metodo HTTP, ruta, descripcion (docstring),
# campos del body (modelos Pydantic), respuesta esperada.
# Genera una entrada por endpoint. NO usar valores genéricos.
PORT = 8000
auth_line = f"X-API-Key: {SERVICE_API_KEY}" if SERVICE_API_KEY else "(sin autenticacion)"
doc = f"""# API Documentation — {ALIAS}
**URL base (desde n8n):** `http://{ALIAS}:{PORT}`
**Autenticacion:** {auth_line}
---
## Endpoints
### GET /health
Verifica que el servicio está activo.
curl:
curl http://{ALIAS}:{PORT}/health -H 'X-API-Key: {SERVICE_API_KEY}'
n8n HTTP Request:
Method: GET
URL: http://{ALIAS}:{PORT}/health
Header X-API-Key: {SERVICE_API_KEY}
Respuesta: {{"status": "ok"}}
---
[El agente agrega aqui una seccion por cada @app.post/@app.get encontrado en main.py,
con el payload real basado en los modelos Pydantic definidos]
---
## Conectar a n8n
1. Crea un workflow en n8n
2. Agrega: [trigger real] → HTTP Request → [logica adicional]
3. Copia la config del endpoint desde este doc al nodo HTTP Request
4. El microservicio ya está disponible en la red interna de Coolify
"""
with open("API_DOCS.md", "w", encoding="utf-8") as f:
f.write(doc)
print("API_DOCS.md generado — compartir con el usuario.")
セキュリティ:標準ルール
| ルール | 説明 |
|---|---|
| 🔒 リポジトリは常に非公開 | 公開リポジトリにクライアントのコードを置かない |
| 🧪 テスト中は一時的な FQDN を使用 | すべてのテストに合格した後に削除します |
| 🔑 SERVICE_API_KEY は任意 | ユーザーが希望する場合は secrets.token_hex(32) を使用し、希望しない場合は省略します |
| 📁 .env はローカルのみ | Coolify が API 経由で変数を注入します |
| 🏷️ わかりやすいエイリアス | クライアント-サービス。例:acme-yt-optimizer |
トラブルシューティング
「接続されている非公開 GitHub Apps がありません」 → ユーザーがまだ GitHub App を作成していません。「前提条件」のセクションを参照してください。
grant_github_app_access が 404 を返す
→ installation_id に「All repositories」の権限がありません。ユーザーは
GitHub → Settings → Applications → [お使いの GitHub App] → Repository access → All repositories に移動する必要があります。
create_application が 422/検証エラーを返す
→ git_repository は正確に owner/repo である必要があります(git@、.git、https:// は含めません)。
custom_network_aliases が適用されない/n8n がサービスに到達できない
→ Coolify API は、複数のフィールドを 1 つの PATCH でまとめて送信すると拒否します。
→ 解決策は、フィールドごとに 個別の PATCH を送信することです。
base = f"{COOLIFY_URL}/api/v1/applications/{APP_UUID}"
requests.patch(base, headers=headers, json={"custom_network_aliases": ALIAS})
requests.patch(base, headers=headers, json={"domains": ""})
requests.patch(base, headers=headers, json={"dockerfile_location": "/Dockerfile"})
公開 FQDN が消えない/fqdn の使用時に "This field is not allowed" と表示される
→ 公開 URL の削除に使用する正しいフィールドは domains です(fqdn ではありません)。
→ 次を使用します:requests.patch(base, headers=headers, json={"domains": ""})
→ その後、再デプロイして Traefik の設定を更新します。
SERVICE_API_KEY がハードコードされている(例:algo-secret-key-2024)
→ 重複した変数は個別の PATCH では更新できません(404 が返されます)。
→ 解決策:古い環境変数を DELETE してから、新しいものを POST します。
envs = requests.get(f"{COOLIFY_URL}/api/v1/applications/{APP_UUID}/envs", headers=headers).json()
for e in [x for x in envs if x["key"] == "SERVICE_API_KEY"]:
requests.delete(f"{COOLIFY_URL}/api/v1/applications/{APP_UUID}/envs/{e['uuid']}", headers=headers)
import secrets
new_key = secrets.token_hex(32)
requests.post(f"{COOLIFY_URL}/api/v1/applications/{APP_UUID}/envs",
headers=headers, json={"key": "SERVICE_API_KEY", "value": new_key})
print(f"Nueva key: {new_key}")
ステップ 5 でテストが失敗する — サービスが 500 エラーを返す
→ ログを確認します:GET /api/v1/deployments/{deployment_uuid} → logs フィールド
→ main.py を修正し、プッシュして再デプロイし、待ってから再テストします。
→ すべてのテストに合格するまで、内部ネットワークへ移行しないでください。
ヘルスチェックに失敗する/running:unknown
→ main.py に 200 を返す GET /health が必要です。
→ dockerfile_location を設定する必要があります(/Dockerfile)。
→ これらの値を自動的に設定する configure_application() を使用します。
n8n がサービスに到達できない(内部ネットワーク)
→ n8n は同じ Coolify(同じ Docker ネットワーク coolify)にインストールする必要があります。
→ http://alias:8000 を使用します。localhost やサーバーの IP アドレスは使用しないでください。
→ n8n が別のサーバーにある場合は、FQDN と永続的な堅牢な認証が必要です。