mcpField
A
agentic-microservice-deployer

agentic-microservice-deployer

github.com/kevinrivmクラウド・DevOps336 回インストール29 週間前に更新
リポジトリ

agentic-microservice-deployer について

ローカルの自動化コードをCoolify上の内部マイクロサービスにし、Dockerネットワーク経由でn8nから利用する手順をAgentに示すスキルです。リポジトリ準備からデプロイ、テスト、エンドポイント文書の作成まで扱います。

主な機能

  1. 01実データのテスト入力を収集
  2. 02GitHubリポジトリの作成と設定
  3. 03Coolifyへの内部サービスのデプロイ
  4. 04デプロイ監視とテスト実行
  5. 05n8n向けエンドポイント文書の生成

活用シーン

  1. 01ローカル自動化を内部APIにまとめる
  2. 02n8nワークフロー向けサービスのデプロイ
  3. 03エンドポイントの検証と呼び出し方法の文書化

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/kevinrivm/agentic-microservice-deployer --skill agentic-microservice-deployer

インストール後、Claude Code で/agentic-microservice-deployerを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
.gitattributes
execution/
coolify_manager.py
github_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. 入力の正確な形式を把握する(誤ったコードにつながる思い込みを避けます)
  2. ユーザーに引き渡す前にサービスを自動テストする
  3. 最初から境界ケースを検証する

少なくとも 1 つのテスト例を用意するまでは、コードを書き始めないでください。

続行する前に、必ずユーザーに尋ねてください。


マイクロサービスを構築する前に、実際のテストデータが必要です。 これにより、次のことが可能になります。

  • データの実際の形式に基づいてコードを構築する
  • 引き渡す前にサービスを自動テストする
  • ユーザーが見つける前にエラーを検出する

次の情報を提供してください。

  1. サービスが受け取る各タイプの入力の実例 (例:OCR 処理するチケットの例、メール全文、画像、サンプル JSON)
  2. その入力に対する期待される応答 (例:「このチケットから日付、合計金額、仕入先を抽出してほしい」)
  3. もしあれば、境界ケースや難しいケース (例:「チケットがぼやけていたり、英語だったりすることがある」)

現時点で実際のデータを用意できない場合は、説明に基づいて合成データを生成できますが、 実際のデータを使うほうが良い結果につながります。


テストデータを受け取ったら、次のようにしてください。

  • 開発中に参照できるよう、.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 分):


  1. Coolify パネル → サイドメニューの 「Sources」
  2. 「Add」 → 「GitHub App」 をクリック
  3. Name:説明的な名前(例:coolify-tudominio)
  4. Organization:空欄のままにする
  5. System Wide:チェックしない
  6. Webhook Endpoint:Coolify の HTTPS ドメインを選択
  7. 「Register Now」 をクリック → GitHub のインストール画面が開きます
  8. GitHub で 「All repositories」 を選択 ← 今後のアクセスをエージェントが管理するために重要です
  9. 「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 と永続的な堅牢な認証が必要です。

コメント