n8nをDockerで起動し、UIを開かずCLIだけでワークフローを実行する
本記事には広告(PR)が含まれます。
セルフホストのn8nを、CIやGUIのないリモートサーバーなどブラウザを開けない環境で自動実行したい人向けの記事。UIを開かずにどこまでの操作がCLIで完結するかを、Docker上のn8n 2.35.4で実測した。
結論
n8nをDockerで起動し、UIを一切開かずにワークフローのインポートから実行、実行結果JSONの取得までがCLIだけで完結することを実機で確認した。
やったことと結果は次のとおり。すべてDocker上のn8n 2.35.4での実測である。
| やったこと | 結果 |
|---|---|
| 新規ボリュームでコンテナを起動 | healthz応答まで約7秒 |
| healthz直後にワークフローをインポート | 成功。エディタ表示の準備完了(15.5秒)を待つ必要はない |
n8n executeでワークフローをCLI実行 | 成功。1回目4,616ms、2回目159ms(初回はGitHub API側の応答待ち) |
| 実行結果の取得 | 標準出力のJSONにノード別実行時間と最終データがすべて入る |
| タスクブローカーのポート競合を切り分け | N8N_RUNNERS_BROKER_PORTの変更だけで解消。N8N_RUNNERS_ENABLED=falseは不要 |
つまずいたのは、ワークフローJSONにidと各ノードのpositionが必須である点と、タスクブローカーのポート競合の2点で、いずれも回避手順を本文に示す。
検証環境
- n8n: 2.35.4(イメージ n8nio/n8n:2.35.4にタグ固定。
docker exec n8n-verify n8n --version→2.35.4で確認) - 実行環境: セルフホスト(Docker Desktop、Docker Engine 28.3.3、Windows 11 Home)
- シェル: Git Bash(curlはcurl.exe)。PowerShellでは
curlがInvoke-WebRequestのエイリアスのため、本記事のコマンドはそのままでは動かない - 検証日: 2026-08-20 JST。本文のログのタイムスタンプはUTC表記
n8nをDockerで起動してCLIでワークフローを実行する手順
1. ワークフローJSONを用意して保存
Manual Trigger → HTTP Request(GitHub APIでn8nリポジトリ情報を取得) → Edit Fields(repo名とスター数を抽出)の3ノード構成。github-repo-check.jsonとしてカレントディレクトリに保存する。
{
"id": "VerifyGithub0001",
"name": "github-repo-check",
"nodes": [
{ "parameters": {}, "name": "Manual Trigger", "type": "n8n-nodes-base.manualTrigger", "typeVersion": 1, "position": [0, 0] },
{ "parameters": { "url": "https://api.github.com/repos/n8n-io/n8n", "options": {} }, "name": "HTTP Request", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [200, 0] },
{ "parameters": { "assignments": { "assignments": [ { "name": "repo", "value": "={{ $json.full_name }}", "type": "string" }, { "name": "stars", "value": "={{ $json.stargazers_count }}", "type": "number" } ] }, "options": {} }, "name": "Edit Fields", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [400, 0] }
],
"connections": { "Manual Trigger": { "main": [[ { "node": "HTTP Request", "type": "main", "index": 0 } ]] }, "HTTP Request": { "main": [[ { "node": "Edit Fields", "type": "main", "index": 0 } ]] } },
"active": false,
"settings": {}
}
ここで2点つまずいた。最上位にidを付けずにインポートすると次のエラーで失敗する。
An error occurred while importing workflows. See log messages for details.
SQLITE_CONSTRAINT: NOT NULL constraint failed: workflow_entity.id
また各ノードにpositionを付けないと次のエラーで失敗する。
An error occurred while importing workflows. See log messages for details.
Workflow structure is invalid. nodes[0].position (invalid_type): Required; nodes[1].position (invalid_type): Required; nodes[2].position (invalid_type): Required
上記JSONは両方とも修正済みの形。
2. docker runで起動し、待つ間にdocker cpでファイルをコピーする
docker cpはn8nの起動完了を待たずに実行できるため、起動直後にコピーを済ませてからhealthzのポーリングに入る。実際に実行した一連のコマンドは次のとおり。
docker run -d --name n8n-verify -p 5678:5678 -v n8n_verify_data:/home/node/.n8n n8nio/n8n:2.35.4
s=$(date +%s.%N)
docker cp github-repo-check.json n8n-verify:/tmp/github-repo-check.json
while :; do
ts=$(date +%s.%N)
h=$(curl -s --max-time 1 -o /dev/null -w "%{http_code}" http://localhost:5678/healthz)
awk -v t="$ts" -v r="$s" -v h="$h" 'BEGIN{printf "+%.2fs healthz=%s\n", t-r, h==""?"000":h}'
[ "$h" = "200" ] && break
sleep 0.5
done
curl -s http://localhost:5678/healthz
出力。docker runはコンテナIDのみを出力し、docker cpは成功時に何も出力しない。経過時間の起点はdocker run完了直後で、初回ポーリングが+2.47sから始まるのはdocker cpの所要時間による。
0ac2201c84fac6d5290f2355ba1a2964b756a44b443a564370ae74b31266c131
+2.47s healthz=000
+3.75s healthz=000
+5.02s healthz=000
+5.76s healthz=000
+6.52s healthz=200
{"status":"ok"}
起動時刻とn8n側のログは次のコマンドで確認できる。
docker inspect -f '{{.State.StartedAt}}' n8n-verify
docker logs -t n8n-verify | grep -E "n8n ready|Editor is now accessible"
出力:
2026-08-19T18:05:15.801687449Z
2026-08-19T18:05:22.891873743Z n8n ready on ::, port 5678
2026-08-19T18:05:31.314518666Z Editor is now accessible via:
コンテナのStartedAt起点で、主要なイベントまでの経過時間は次のとおり。
n8n readyログまで7.1秒Editor is now accessibleログまで15.5秒docker run完了はStartedAtの0.6秒後- healthz 200の観測(
docker run完了起点で+6.52s)は、StartedAt起点では約7.1秒に相当し、n8n readyログとほぼ同時
新規ボリュームの初回起動ではDBマイグレーションが走る。総数は次のコマンドで確認できる。
docker logs n8n-verify 2>&1 | grep -c "Starting migration"
出力:
234
なお、この秒数とマイグレーション挙動は新規ボリューム前提の実測である。再実行する場合はdocker rm -f n8n-verifyとdocker volume rm n8n_verify_dataで作り直すこと。次の手順のとおり、インポートはhealthz 200の直後から可能だった。
3. n8n import:workflowでワークフローをインポートする
docker exec n8n-verify n8n import:workflow --input=/tmp/github-repo-check.json
出力:
Importing 1 workflows...
Successfully imported 1 workflow.
このインポートはhealthzが200を返した直後(起点+6.8秒)に開始し、+12.5秒時点で完了した。開始・完了の経過時間は手順2と同一スクリプト内で続けて計測したものである。手順2でファイルをコピー済みなら、Editor is now accessible(StartedAt起点15.5秒)より前でも、healthzが200を返せばインポートは通ることを実測で確認した。
4. 同一コンテナでn8n executeを実行するとport 5679 is already in useで失敗する
環境変数を付けずにn8n executeを実行すると、常駐プロセスがタスクブローカーの5679番ポートを使用中のため失敗する。
docker exec n8n-verify n8n execute --id=VerifyGithub0001
出力:
n8n Task Broker's port 5679 is already in use. Do you have another instance of n8n running already?
N8N_RUNNERS_ENABLED=falseだけを付けても同じエラーで失敗する。
docker exec -e N8N_RUNNERS_ENABLED=false n8n-verify n8n execute --id=VerifyGithub0001
出力:
n8n Task Broker's port 5679 is already in use. Do you have another instance of n8n running already?
5. N8N_RUNNERS_BROKER_PORTでポート競合を解消してCLI実行する
N8N_RUNNERS_BROKER_PORTでブローカーのポートを変更すると、これだけで成功する。N8N_RUNNERS_ENABLED=falseは不要。
docker exec -e N8N_RUNNERS_BROKER_PORT=5680 n8n-verify n8n execute --id=VerifyGithub0001
実行に成功すると、出力冒頭にn8n Task Broker ready on 127.0.0.1, port 5680が出てポート変更が効いたことが確認でき、Execution was successful:に続けて実行結果のJSON全体が標準出力に出る。別コマンドを実行する必要はない。JSON内の主な構造は次のとおり。
data直下にはversion・startDataがあり、resultDataの後にはexecutionData・resumeTokenが続く- ノード別の実行時間・開始終了時刻・最終データは
data.resultData.runData以下にノード名ごとに格納されている - runDataの各要素の先頭には
startTime・executionIndex・source・hintsがあり、data内には各ノードの出力本体が入る
以下は2回目の実行の抜粋で、...は省略箇所を示す。
n8n Task Broker ready on 127.0.0.1, port 5680
(中略)
Execution was successful:
====================================
{
"data": {
...,
"resultData": {
"runData": {
"Manual Trigger": [
{ ..., "executionTime": 2, "executionStatus": "success", ... }
],
"HTTP Request": [
{ ..., "executionTime": 109, "executionStatus": "success", ... }
],
"Edit Fields": [
{
...,
"executionTime": 40,
"executionStatus": "success",
"data": { "main": [[ { "json": { "repo": "n8n-io/n8n", "stars": 201192 }, ... } ]] }
}
]
},
"lastNodeExecuted": "Edit Fields"
},
...
},
"mode": "cli",
"startedAt": "2026-08-19T18:06:40.715Z",
"stoppedAt": "2026-08-19T18:06:40.874Z",
"storedAt": "db",
"status": "success",
"finished": true
}
比較のため、1回目の実行の抜粋も同じ形で示す。所要時間4,616msの大半がHTTP Requestノードの4,562msで、初回の遅さがGitHub API側の応答待ちによるものと内訳から分かる。
n8n Task Broker ready on 127.0.0.1, port 5680
(中略)
Execution was successful:
====================================
{
"data": {
...,
"resultData": {
"runData": {
"Manual Trigger": [
{ ..., "executionTime": 1, "executionStatus": "success", ... }
],
"HTTP Request": [
{ ..., "executionTime": 4562, "executionStatus": "success", ... }
],
"Edit Fields": [
{ ..., "executionTime": 42, "executionStatus": "success", ... }
]
},
"lastNodeExecuted": "Edit Fields"
},
...
},
"mode": "cli",
"startedAt": "2026-08-19T18:06:31.724Z",
"stoppedAt": "2026-08-19T18:06:36.340Z",
"storedAt": "db",
"status": "success",
"finished": true
}
実行時にFailed to start Python task runner ... Python 3 is missingという警告が出るが、JSのみで構成したワークフローはこの警告と無関係にsuccessで完走する。
起動時間とCLI実行時間の実測結果
- 起動(新規ボリューム): StartedAt起点で
n8n readyまで7.1秒、Editor is now accessibleまで15.5秒。healthz 200の観測はdocker run完了起点で+6.52s(StartedAt起点の約7.1秒に相当)で、readyログとほぼ同時 - healthz 200直後のインポート:
docker run完了起点で+6.8秒に開始し成功(+12.5秒に完了)。事前にdocker cpを済ませておけば、Editor is now accessibleを待たなくても通る - CLI実行1回目: startedAt 18:06:31.724Z、stoppedAt 18:06:36.340Z(4,616ms)。内訳はManual Trigger 1ms、HTTP Request 4,562ms、Edit Fields 42ms(合計4,605ms)で、全体との差11msがワークフロー前後の処理。初回の遅さはGitHub API側の応答によるもの
- CLI実行2回目: startedAt 18:06:40.715Z、stoppedAt 18:06:40.874Z(159ms)。内訳はManual Trigger 2ms、HTTP Request 109ms、Edit Fields 40ms(合計151ms)で、全体との差8msがワークフロー前後の処理
- 実行結果:
{"repo": "n8n-io/n8n", "stars": 201192} - ポート競合の切り分け: 環境変数なしと
N8N_RUNNERS_ENABLED=falseのみはどちらも「port 5679 is already in use」で失敗し、N8N_RUNNERS_BROKER_PORT=5680のみで成功した。必要なのはブローカーポートの変更のみで、N8N_RUNNERS_ENABLED=falseは単独では効果がない
CLI実行の結果をUIの実行履歴で確認する
ここまでの手順はUIを一切開かずに完結しているが、CLI実行の結果はDBに保存されており(実行出力JSONのstoredAtがdb)、後からエディタ画面でも確認できる。以下のスクリーンショットは、本記事と同一の手順を同じ検証環境で再実行して取得したもので、本文に引用したログとは別の試行である。この試行の所要時間は1回目1.358秒、2回目532msで、引用した試行の4,616ms・159msとは一致しない。所要時間はGitHub API側の応答に左右されるため、初回が遅く2回目が速い傾向だけが再現する。また画面の時刻表示はブラウザのローカル時刻(JST)で、本文ログのUTC表記とは9時間ずれる。
UIの閲覧は検証の本筋ではないため手順には含めない。http://localhost:5678/への初回アクセス時のみオーナーアカウントの作成を求められ、メールアドレス・氏名・パスワードを登録する。以下は作成を済ませた状態の画面で、ワークフロー一覧からgithub-repo-checkを開くとJSONで定義した3ノードが表示され、画面上部のExecutionsタブに切り替えると実行履歴が見える。
この試行でこのワークフローを実行したのはCLIからの2回のみで、履歴の2件はそれに対応する。同一手順の再実行のため、この2件の実行も本文に引用した実行出力JSONと同じくmodeはcliである。
まとめ
- n8nはDockerコンテナ上で
n8n executeを使えば、UIを開かずインポートから実行、結果の取得までCLIだけで完結する - 起動待ちはhealthzのポーリングで足り、healthzが返ればインポートは通る
- ワークフローJSONには
idと各ノードのpositionが必須 - 同一コンテナ内でCLI実行する場合は、タスクブローカーのポート競合を
N8N_RUNNERS_BROKER_PORTで回避する - ノード別の実測値や最終データは実行出力のJSONにすべて含まれており、別途取得コマンドを実行する必要はない
出典
- https://docs.n8n.io/hosting/installation/docker/ (2026-08-20取得)
- https://docs.n8n.io/hosting/cli-commands/ (2026-08-20取得)
- https://docs.n8n.io/hosting/configuration/task-runners/ (2026-08-20取得)

