目次
前提(準備)
GCP プロジェクト:n8n-chelsea-labs
(課金有効)
Supabase:プロジェクト作成済み。以下4つを控える
SUPABASE_HOST
:例db.xxxxx.supabase.co
(RESTの https://xxxx.supabase.co ではなく DB のホスト)SUPABASE_PORT
:通常5432
SUPABASE_DATABASE
:通常postgres
SUPABASE_USER
:通常postgres
SUPABASE_PASSWORD
:作成時に決めたDBパスワード
ローカルPCに以下導入済み
- Node.js LTS(推奨 18 以上)
- Pulumi CLI
- gcloud CLI(ログイン済み)
# GCPログイン & プロジェクト選択
gcloud auth login
gcloud config set project n8n-chelsea-labs
# ADC(Application Default Credentials)も設定しておくとPulumiから認証が通りやすい
gcloud auth application-default login
権限目安(自分のアカウントに付与):roles/owner
があれば十分です。最小構成ならroles/run.admin
, roles/iam.serviceAccountAdmin
,roles/secretmanager.admin
, roles/serviceusage.serviceUsageAdmin
など。
Compute Engine API
gcloud で一括有効化
gcloud services enable \
compute.googleapis.com \
run.googleapis.com \
secretmanager.googleapis.com \
--project=n8n-chelsea-labs
1) プロジェクト初期化
mkdir n8n-pulumi && cd n8n-pulumi
pulumi new gcp-typescript
# 対話で:
# Project name: n8n-pulumi (任意)
# Stack name: dev (任意)
# gcp:project: n8n-chelsea-labs
# gcp:region: asia-northeast1
Pulumi の状態(state)保存はデフォルトで Pulumi Cloud。ローカルにしたい場合は
pulumi login file://~/.pulumi を先に実行。
1)Pulumi Cloud にログイン(推奨)
https://app.pulumi.com/user/settings/tokens
の Create token をクリック- 説明(例:
n8n-pulumi-dev
)と期限を入れて Create - 表示された access token をコピー(この画面でしか見えません!)
- さっきのターミナルのプロンプトに ペーストして Enter
Enter your access token from https://app.pulumi.com/account/tokens
or hit <ENTER> to log in using your browser : <ここに貼り付け>
Pulumi プロジェクトの初期化
プロンプトが出ている:
Project name (n8n-pulumi):
そのまま Enter を押すと、デフォルト値の n8n-pulumi が採用されます。
(別の名前を使いたければ入力してから Enter)
続けて以下の質問が出ます:
Project description (A minimal Google Cloud TypeScript Pulumi program):
→ Enter(デフォルトでOK)
Node.js/npm がインストールされていない環境
下記のようなメッセージが出る場合があり、Node.js をインストールする必要がある。
The package manager to use for installing dependencies [Use arrows to move, type to filter]
> npm [not found]
pnpm [not found]
yarn [not found]
bun [not found]
✅ Pulumi プロジェクト作り直し
Node.js が入ったら、もう一度 Pulumi プロジェクトを作り直すのがスッキリします。
rm -rf n8n-pulumi
mkdir n8n-pulumi && cd n8n-pulumi
pulumi new gcp-typescript
2) 依存パッケージの追加
npm i -D typescript ts-node @types/node
npm i @pulumi/pulumi @pulumi/gcp @pulumi/random @pulumi/command
- package.json
"scripts": {
"build": "tsc",
"up": "pulumi up",
"preview": "pulumi preview",
"destroy": "pulumi destroy"
}
✅ 修正後の package.json
{
"name": "n8n-pulumi",
"main": "index.ts",
"scripts": {
"build": "tsc",
"up": "pulumi up",
"preview": "pulumi preview",
"destroy": "pulumi destroy"
},
"devDependencies": {
"@types/node": "^18.19.123",
"ts-node": "^10.9.2",
"typescript": "^5.9.2"
},
"dependencies": {
"@pulumi/command": "^1.1.0",
"@pulumi/gcp": "^8.41.1",
"@pulumi/pulumi": "^3.191.0",
"@pulumi/random": "^4.18.3"
}
}
- .gitignore
/Pulumi.*.yaml
✅ 修正後の .gitignore
/bin/
/node_modules/
/Pulumi.*.yaml
3) Pulumi Config に値を登録
Pulumi スタックを選択(dev 例)
pulumi stack select dev
現状の設定を確認する
pulumi config
設定する項目
# GCP 設定(new時に入れていなければ)
pulumi config set gcp:project n8n-chelsea-labs
pulumi config set gcp:region asia-northeast1
# Supabase 接続情報
pulumi config set supabaseHost "db.xxxxx.supabase.co"
pulumi config set supabasePort "5432"
pulumi config set supabaseDatabase "postgres"
pulumi config set supabaseUser "postgres"
pulumi config set --secret supabasePassword "YOUR_SUPABASE_DB_PASSWORD"
4) index.ts
を作成(実装本体)
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
import * as random from "@pulumi/random";
import * as command from "@pulumi/command";
// ===== Config =====
const cfg = new pulumi.Config();
const project = gcp.config.project!;
const region = gcp.config.region!; // pulumi config の gcp:region を利用
const supabaseHost = cfg.require("supabaseHost");
const supabasePort = cfg.get("supabasePort") || "5432";
const supabaseDatabase = cfg.get("supabaseDatabase") || "postgres";
const supabaseUser = cfg.get("supabaseUser") || "postgres";
const supabasePassword = cfg.requireSecret("supabasePassword");
// ===== Enable required APIs =====
const requiredApis = ["run.googleapis.com", "secretmanager.googleapis.com"];
const apiEnablers = requiredApis.map((svc) =>
new gcp.projects.Service(svc.replace(/\./g, "-"), {
service: svc,
disableDependentServices: true,
// disableOnDestroy: false, // 必要に応じて
})
);
// ===== Service Account =====
const sa = new gcp.serviceaccount.Account("n8n-service-account", {
accountId: "n8n-service-account",
displayName: "n8n Service Account",
});
// ===== Secret Manager: Supabase DB password =====
const dbPasswordSecret = new gcp.secretmanager.Secret("supabase-db-password", {
secretId: "supabase-db-password",
replication: { auto: {} },
}, { dependsOn: apiEnablers });
const dbPasswordVersion = new gcp.secretmanager.SecretVersion("supabase-db-password-v", {
secret: dbPasswordSecret.id,
secretData: supabasePassword,
});
// ===== Secret Manager: N8N_ENCRYPTION_KEY(ランダム生成) =====
const encKey = new random.RandomPassword("n8n-encryption-key", { length: 32, special: false });
const encKeySecret = new gcp.secretmanager.Secret("n8n-encryption-key-secret", {
secretId: "n8n-encryption-key",
replication: { auto: {} },
}, { dependsOn: apiEnablers });
const encKeyVersion = new gcp.secretmanager.SecretVersion("n8n-encryption-key-v", {
secret: encKeySecret.id,
secretData: encKey.result.apply(v => v), // Output<string> を渡す
});
// ===== IAM: Cloud Run の SA に Secret 読み取り権限 =====
const dbSecretIam = new gcp.secretmanager.SecretIamMember("db-secret-accessor", {
secretId: dbPasswordSecret.secretId,
role: "roles/secretmanager.secretAccessor",
member: pulumi.interpolate`serviceAccount:${sa.email}`,
});
const encSecretIam = new gcp.secretmanager.SecretIamMember("enc-secret-accessor", {
secretId: encKeySecret.secretId,
role: "roles/secretmanager.secretAccessor",
member: pulumi.interpolate`serviceAccount:${sa.email}`,
});
// ===== Cloud Run (v2) Service =====
const serviceName = "n8n-supabase-service";
const n8n = new gcp.cloudrunv2.Service("n8n-service", {
name: serviceName,
location: region,
deletionProtection: false,
template: {
serviceAccount: sa.email,
scaling: {
minInstanceCount: 1, // コールドスタート回避(課金に注意)
maxInstanceCount: 5,
},
containers: [{
image: "docker.io/n8nio/n8n:latest",
ports: { containerPort: 5678 },
resources: { limits: { cpu: "1", memory: "1Gi" } },
envs: [
{ name: "DB_TYPE", value: "postgresdb" },
{ name: "DB_POSTGRESDB_HOST", value: supabaseHost },
{ name: "DB_POSTGRESDB_PORT", value: supabasePort },
{ name: "DB_POSTGRESDB_DATABASE", value: supabaseDatabase },
{ name: "DB_POSTGRESDB_USER", value: supabaseUser },
{ name: "DB_POSTGRESDB_SCHEMA", value: "public" },
{ name: "DB_POSTGRESDB_SSL", value: "true" }, // Supabase は SSL 必須
{ name: "N8N_DIAGNOSTICS_ENABLED", value: "false" }, // 任意:テレメトリ無効
// パスワード & 暗号鍵は Secret Manager 参照
{
name: "DB_POSTGRESDB_PASSWORD",
valueSource: { secretKeyRef: { secret: dbPasswordSecret.name, version: "latest" } },
},
{
name: "N8N_ENCRYPTION_KEY",
valueSource: { secretKeyRef: { secret: encKeySecret.name, version: "latest" } },
},
// 必要なら後で N8N_HOST / PROTOCOL / PORT を追加(Cloud Run は自動でHTTPS化される)
],
}],
},
}, { dependsOn: [dbSecretIam, encSecretIam, ...apiEnablers] });
// ===== IAM: 公開アクセス(未認証Invoker) =====
const invoker = new gcp.cloudrunv2.ServiceIamMember("n8n-invoker", {
name: n8n.name,
location: region,
role: "roles/run.invoker",
member: "allUsers",
}, { dependsOn: n8n }); // ← サービス作成後に実行させる
// =====(オプション)デプロイ後にベースURLを注入 =====
// Cloud Run の URL はデプロイ後に確定するため、gcloud で追い書き
const setBaseUrl = new command.local.Command("set-base-url", {
create: pulumi.interpolate`gcloud run services update ${serviceName} --project=${project} --region=${region} --set-env-vars=WEBHOOK_URL=${n8n.uri},N8N_EDITOR_BASE_URL=${n8n.uri} --format=none`,
}, { dependsOn: n8n });
// ===== Outputs =====
export const n8nServiceAccountEmail = sa.email;
export const n8nServiceUrl = n8n.uri;
5) デプロイ
pulumi preview # 作成プランを確認
pulumi up # yes で実行
コメント