Mac miniのDockerでNeo4j 5 + APOCを起動し、CSV読み込みまで確認する
Mac mini上のDockerでNeo4jを動かし、Graph RAGや金融データ分析、購買管理、在庫・レシピ管理などに使えるグラフDB環境を作っていきます。
以前、M1 MacBook AirでNeo4jをDockerから起動したことがありました。
今回はMac mini側で、最初から以下を意識した構成にします。
- Docker Composeで管理する
- Neo4j 5を使う
- APOC Coreを有効化する
- JSON / CSV入出力を扱えるようにする
importディレクトリを固定する- 外部公開せず、SSHトンネル経由で安全に使う
- 今回の目的
- 作業フォルダを作成する
- docker-compose.ymlを作成する
- .envを作成する
- importフォルダを作成する
- Neo4jを起動する
- コンテナの状態を確認する
- ログを確認する
- 原因:Neo4jのメモリ指定が大きすぎた
- 対処:メモリ指定をコメントアウトする
- コンテナを停止・削除する
- 再度起動する
- Mac mini上でNeo4jのHTTP応答を確認する
- MacBook AirからSSHトンネルで接続する
- Neo4j Browserへログインする
- 最初は1DB構成で進める
- APOCの動作確認
- JSON関連のAPOC機能を確認する
- CSV読み込み用のテストファイルを作成する
- apoc.load.csvは使えなかった
- 標準のLOAD CSVを使う
- CSVを読み込んでAssetノードを作成する
- 重複防止の制約を作成する
- 更新時刻とデータ元を付ける
- 時系列データはAsset直書きより分離した方がよい
- 電源断後にDocker Desktopが起動していなかった
- AssetとPriceの関係を確認する
- CSVからAssetとPriceを作成する
- 同じ日時でPriceが増えすぎる問題
- 1銘柄×1日で重複を抑える
- 注意:Dateノードだけにvalueを持たせると値が混ざる
- 改善案:DailyPriceノードを使う
- 改善案:制約も作成しておく
- 今回の到達点
- ハマりどころ
- 次にやること
- まとめ
今回の目的
今回の目的は、Mac mini上でNeo4jを安定して起動し、APOCとCSV読み込みを確認することです。
最終的には、以下のような用途に使う予定です。
- 自動売買・金融分析
- 為替・経済指標データの関係管理
- 購買管理
- 店舗・価格データの管理
- 在庫・レシピ管理
- 防災・非常食管理
- 株式・四季報・企業関係の整理
- Graph RAG
作業フォルダを作成する
まず、ホームディレクトリにNeo4j用の作業フォルダを作成します。
mkdir -p ~/neo4j-stack cd ~/neo4j-stack
docker-compose.ymlを作成する
docker-compose.yml を作成します。
vim docker-compose.yml
最初に作成した内容は以下です。
version: "3.9"
services:
neo4j:
image: neo4j:5
container_name: neo4j
restart: unless-stopped
# 安全寄り:Mac mini 内だけで使う(外部からは SSH トンネル推奨)
# MacBook Air から LAN 直で繋ぐなら 127.0.0.1 を外して "7474:7474" / "7687:7687" に変更
ports:
- "127.0.0.1:7474:7474" # Neo4j Browser (HTTP)
- "127.0.0.1:7687:7687" # Bolt
environment:
# 認証(.env に書く想定)
NEO4J_AUTH: ${NEO4J_AUTH}
# APOC Core(互換版を自動取得)
NEO4J_PLUGINS: '["apoc"]'
# セキュリティ:APOC のみ許可(まず安全側)
NEO4J_dbms_security_procedures_allowlist: "apoc.*"
# JSON/CSV をファイルで扱う(購買管理・財務データ・指標データ取り込みに必須)
NEO4J_apoc_import_file_enabled: "true"
NEO4J_apoc_export_file_enabled: "true"
NEO4J_apoc_import_file_use__neo4j__config: "true"
# ファイルアクセスは /import のみに固定(安全)
NEO4J_dbms_directories_import: "/import"
# 自動売買/分析用途:安定化(まずは控えめ、必要なら増やす)
NEO4J_dbms_memory_heap_initial__size: "2G"
NEO4J_dbms_memory_heap_max__size: "6G"
NEO4J_dbms_memory_pagecache_size: "4G"
# 追加のログ(必要なら)
# NEO4J_dbms_logs_query_enabled: "true"
# NEO4J_dbms_logs_query_threshold: "2s"
# 同時接続・大量処理の安全マージン
ulimits:
nofile:
soft: 40000
hard: 40000
volumes:
# データとログは named volume(権限事故が少なく安定)
- neo4j_data:/data
- neo4j_logs:/logs
# import はホストのフォルダに(JSON/CSV を Finder で置ける)
- ./import:/import
volumes:
neo4j_data:
neo4j_logs:
ただし、この設定は後でメモリ設定が原因で起動に失敗します。
そのため、最終的にはメモリ指定部分をコメントアウトします。
.envを作成する
同じディレクトリに .env を作成します。
vim .env
内容は以下です。
NEO4J_AUTH=neo4j/ChangeMe_1234_8chars
NEO4J_AUTH は、Neo4jの初期ユーザーとパスワードを指定する設定です。
今回は検証用なのでこの値にしていますが、実運用では必ず強いパスワードに変更します。
importフォルダを作成する
CSVやJSONを配置するための import フォルダを作成します。
mkdir -p import
Neo4jを起動する
Docker Composeで起動します。
docker compose up -d
実行結果は以下です。
WARN[0000] /Users/snowpool/neo4j-stack/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion [+] Running 9/9 ✔ neo4j Pulled 14.6s ✔ 4f4fb700ef54 Pull complete 1.0s ✔ 40745bfacd19 Pull complete 1.0s ✔ c192da3a442c Pull complete 1.0s ✔ 4cb50a53c4c3 Pull complete 10.0s ✔ 2b952d66bc8c Pull complete 2.3s ✔ ff17433fd932 Pull complete 9.5s ✔ c6f8b1ffa89b Download complete 0.0s ✔ 5a70d00e19a9 Download complete 0.1s [+] Running 4/4 ✔ Network neo4j-stack_default Created ✔ Volume neo4j-stack_neo4j_data Created ✔ Volume neo4j-stack_neo4j_logs Created ✔ Container neo4j Started
version がobsoleteという警告が出ていますが、これはDocker Composeの新しい仕様では version 属性が不要になっているためです。
この時点ではコンテナが起動したように見えます。
コンテナの状態を確認する
コンテナの状態を確認します。
docker ps
結果は以下でした。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c026244bbc0f neo4j:5 "tini -g -- /startup…" 32 seconds ago Restarting (3) 3 seconds ago neo4j
Restarting になっており、正常起動していません。
ログを確認する
原因を確認するためにログを見ます。
docker logs neo4j --tail 200
重要なエラーは以下です。
ERROR Invalid memory configuration - exceeds physical memory. Check the configured values for server.memory.pagecache.size and server.memory.heap_max_size
Neo4jに割り当てたメモリ量が、Docker Desktopが認識している物理メモリ上限を超えているため、起動直後に終了していました。
原因:Neo4jのメモリ指定が大きすぎた
原因になっていたのは、以下の設定です。
NEO4J_dbms_memory_heap_initial__size: "2G" NEO4J_dbms_memory_heap_max__size: "6G" NEO4J_dbms_memory_pagecache_size: "4G"
合計で約10GBをNeo4jに要求しています。
macOS上のDocker Desktopでは、ホストMacのメモリが16GBあっても、Docker Desktop側に割り当てられているメモリ上限が小さい場合があります。
そのため、Neo4jから見ると物理メモリ不足になり、以下のエラーで終了していました。
Invalid memory configuration - exceeds physical memory
対処:メモリ指定をコメントアウトする
今回はまずNeo4jを起動することを優先し、メモリ指定をコメントアウトしました。
# NEO4J_dbms_memory_heap_initial__size: "2G" # NEO4J_dbms_memory_heap_max__size: "6G" # NEO4J_dbms_memory_pagecache_size: "4G"
必要になったら、Docker Desktop側のメモリ割り当てと合わせて、後から調整する方針にします。
コンテナを停止・削除する
設定を修正したら、いったんコンテナを停止・削除します。
docker compose down
結果は以下です。
WARN[0000] /Users/snowpool/neo4j-stack/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion [+] Running 2/2 ✔ Container neo4j Removed ✔ Network neo4j-stack_default Removed
docker compose down により、コンテナ停止、コンテナ削除、作成されたネットワークの削除が行われます。
再度起動する
修正後、再度起動します。
docker compose up -d
結果は以下です。
WARN[0000] /Users/snowpool/neo4j-stack/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion [+] Running 1/1 ✔ neo4j Pulled [+] Running 2/2 ✔ Network neo4j-stack_default Created ✔ Container neo4j Started
コンテナ状態を確認します。
docker ps
今度は正常に起動しました。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e766ab83404e neo4j:5 "tini -g -- /startup…" 40 seconds ago Up 40 seconds 127.0.0.1:7474->7474/tcp, 127.0.0.1:7687->7687/tcp neo4j
Mac mini上でNeo4jのHTTP応答を確認する
Mac mini上で、Neo4j Browser用のHTTPポートを確認します。
curl http://localhost:7474
結果は以下です。
{"bolt_routing":"neo4j://localhost:7687","query":"http://localhost:7474/db/{databaseName}/query/v2","transaction":"http://localhost:7474/db/{databaseName}/tx","bolt_direct":"bolt://localhost:7687","neo4j_version":"5.26.19","neo4j_edition":"community"}
Neo4j 5.26.19 Community Editionが起動していることを確認できました。
MacBook AirからSSHトンネルで接続する
今回はNeo4jのポートをMac mini内の 127.0.0.1 に閉じています。
そのため、MacBook Airから直接LANで接続するのではなく、SSHトンネルを使います。
MacBook Air側で以下を実行します。
ssh -N -L 7474:localhost:7474 -L 7687:localhost:7687 macmini
この状態で、MacBook Airのブラウザから以下へアクセスします。
Neo4j Browserが表示されれば成功です。
Neo4j Browserへログインする
ログイン時の設定は以下です。
- Connect URL:
neo4j://localhost:7687 - Username:
neo4j - Password:
ChangeMe_1234_8chars - Database:空欄のままでOK
Database は空欄で問題ありません。
Neo4j Community Editionの単一DB構成では、デフォルトデータベースである neo4j に接続されます。
最初は1DB構成で進める
今回の用途では、最初は1つのデータベースにまとめ、ノードラベルで分ける方針にします。
例えば、以下のようなデータを扱う予定です。
- 自動売買・為替・経済指標
- 購買管理・店舗・価格
- 在庫・レシピ
- 防災・非常食
- 株式・四季報・企業関係
- Graph RAG
データベースを分けるのは、データ量や権限管理が問題になってからで十分と判断しました。
APOCの動作確認
ログイン後、APOCが使えるか確認します。
RETURN apoc.version();
結果は以下です。
apoc.version() "5.26.19"
Neo4j 5.26.xに対応するAPOC Coreがロードされています。
これで、Docker、plugins、allowlist設定は成功です。
JSON関連のAPOC機能を確認する
次に、JSON関連のAPOC機能を確認します。
CALL apoc.help("json");
結果として、以下のようなJSON関連のprocedureやfunctionが表示されました。
apoc.export.json.all apoc.export.json.data apoc.export.json.graph apoc.export.json.query apoc.import.json apoc.load.json apoc.load.jsonArray apoc.load.jsonParams apoc.convert.fromJsonList apoc.convert.fromJsonMap apoc.convert.toJson apoc.json.path
これで、Graph RAG、JSONインポート、エクスポート、高度なグラフ操作に進める状態になりました。
CSV読み込み用のテストファイルを作成する
次に、CSV読み込みを試します。
Mac mini側で import/test.csv を作成します。
vim import/test.csv
内容は以下です。
id,name,value 1,USDJPY,147.25 2,EURUSD,1.084 3,WTI,78.3
apoc.load.csvは使えなかった
最初に、APOCのCSV読み込みを試しました。
CALL apoc.load.csv("file:///test.csv") YIELD map
RETURN map;
しかし、以下のエラーになりました。
Neo.ClientError.Procedure.ProcedureNotFound There is no procedure with the name `apoc.load.csv` registered for this database instance. Please ensure you've spelled the procedure name correctly and that the procedure is properly deployed.
apoc.load.csv が登録されていないようです。
確認のため、以下を実行しました。
CALL apoc.help("load.csv");
結果は何も返りませんでした。
(no changes, no records)
さらに、APOCのload系procedureを確認します。
SHOW PROCEDURES YIELD name WHERE name STARTS WITH "apoc.load." RETURN name ORDER BY name;
結果は以下です。
name "apoc.load.json" "apoc.load.jsonArray" "apoc.load.jsonParams" "apoc.load.xml"
この環境では、apoc.load.csv は使えないことが分かりました。
標準のLOAD CSVを使う
Neo4jには標準で LOAD CSV があります。
そのため、CSV読み込みは標準の LOAD CSV を使います。
LOAD CSV WITH HEADERS FROM 'file:///test.csv' AS row RETURN row LIMIT 10;
結果は以下です。
row
{
"id": "1",
"name": "USDJPY",
"value": "147.25"
}
{
"id": "2",
"name": "EURUSD",
"value": "1.084"
}
{
"id": "3",
"name": "WTI",
"value": "78.3"
}
import/test.csv を問題なく読み込めています。
CSVを読み込んでAssetノードを作成する
CSVから Asset ノードを作成します。
LOAD CSV WITH HEADERS FROM 'file:///test.csv' AS row
MERGE (a:Asset {name: row.name})
SET a.value = toFloat(row.value);
中身を確認します。
MATCH (a:Asset) RETURN a;
結果は以下です。
╒═══════════════════════════════════════╕
│a │
╞═══════════════════════════════════════╡
│(:Asset {name: "USDJPY",value: 147.25})│
├───────────────────────────────────────┤
│(:Asset {name: "EURUSD",value: 1.084}) │
├───────────────────────────────────────┤
│(:Asset {name: "WTI",value: 78.3}) │
└───────────────────────────────────────┘
重複防止の制約を作成する
MERGE (a:Asset {name: row.name}) を使っているため、理論上は同じnameのAssetは重複しにくいです。
ただし、実務では制約を張っておく方が安全です。
CREATE CONSTRAINT asset_name_unique IF NOT EXISTS FOR (a:Asset) REQUIRE a.name IS UNIQUE;
結果は以下です。
Added 1 constraint, completed after 52 ms.
更新時刻とデータ元を付ける
自動売買や分析用途では、値だけでなく「いつ・どこから来たデータか」が重要です。
そこで、更新時刻とデータ元を追加します。
LOAD CSV WITH HEADERS FROM 'file:///test.csv' AS row
MERGE (a:Asset {name: row.name})
SET
a.value = toFloat(row.value),
a.updated_at = datetime(),
a.source = "test_csv";
結果は以下です。
Set 9 properties, completed after 31 ms.
時系列データはAsset直書きより分離した方がよい
ここまでの方法では、最新値を Asset ノードに直接書いています。
ただし、為替やコモディティのように時系列を扱う場合は、価格データを別ノードに分けた方が扱いやすくなります。
考え方としては、以下のようなモデルです。
(:Asset)-[:HAS_PRICE]->(:Price)
ただし、これは図のパターン表記です。
そのままNeo4j Browserで実行するとエラーになります。
Cypherとして実行するには、MATCH、CREATE、MERGE などの命令文が必要です。
電源断後にDocker Desktopが起動していなかった
途中でコンセント工事のため、Mac miniの電源を落としました。
再度電源を入れた後、Neo4jを起動しようとすると、以下のエラーになりました。
cd ~/neo4j-stack docker compose up -d
WARN[0000] /Users/snowpool/neo4j-stack/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion unable to get image 'neo4j:5': Cannot connect to the Docker daemon at unix:///Users/snowpool/.docker/run/docker.sock. Is the docker daemon running?
原因は、Docker Desktopが起動していないことでした。
MacではDocker Desktopを起動しないと、Docker daemonに接続できません。
以下でDocker Desktopを起動します。
open -a Docker
その後、再度起動します。
docker compose up -d
結果は以下です。
WARN[0000] /Users/snowpool/neo4j-stack/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion [+] Running 1/1 ✔ Container neo4j Running
これでNeo4jが再び動作するようになりました。
AssetとPriceの関係を確認する
続きとして、まず既存の Asset と Price の関係を確認します。
MATCH p = (:Asset)-[:HAS_PRICE]->(:Price) RETURN p LIMIT 25;
まだ作成していない場合は、何も返りません。
CSVからAssetとPriceを作成する
今回の test.csv には、id、name、value だけがあり、観測日時はありません。
そのため、まずは datetime() で現在時刻を入れる形にします。
LOAD CSV WITH HEADERS FROM 'file:///test.csv' AS row
MERGE (a:Asset {name: row.name})
CREATE (p:Price {
value: toFloat(row.value),
observed_at: datetime(),
source: 'test.csv'
})
MERGE (a)-[:HAS_PRICE]->(p);
中身を確認します。
MATCH p = (a:Asset)-[:HAS_PRICE]->(pr:Price) RETURN a.name, pr.value, pr.observed_at ORDER BY pr.observed_at DESC LIMIT 50;
結果は以下です。
a.name pr.value pr.observed_at "USDJPY" 147.25 "2026-01-10T21:49:59.314000000Z" "EURUSD" 1.084 "2026-01-10T21:49:59.314000000Z" "WTI" 78.3 "2026-01-10T21:49:59.314000000Z"
同じ日時でPriceが増えすぎる問題
上記の方法では、CSVを読み込むたびに新しい Price ノードが作られます。
履歴として残すならそれでもよいですが、同じデータを何度も読み込むと、Price が増えすぎる可能性があります。
本来は、CSV側に観測日や時刻を入れるのがベストです。
ただし、今回のようにCSVに日付がない場合は、「今日の最新値」として扱う方法もあります。
1銘柄×1日で重複を抑える
とりあえず、1銘柄×1日で1レコードに落ち着くようにします。
LOAD CSV WITH HEADERS FROM 'file:///test.csv' AS row
MERGE (a:Asset {name: row.name})
MERGE (d:Date {date: date()})
MERGE (a)-[:HAS_PRICE]->(d)
SET d.value = toFloat(row.value),
d.source = 'test.csv',
d.updated_at = datetime();
確認します。
MATCH (a:Asset)-[:HAS_PRICE]->(d:Date) RETURN a.name, d.date, d.value ORDER BY d.date DESC, a.name LIMIT 50;
結果は以下です。
a.name d.date d.value "EURUSD" "2026-01-10" 78.3 "USDJPY" "2026-01-10" 78.3 "WTI" "2026-01-10" 78.3
ただし、この結果を見ると、すべての Asset が同じ Date ノードに接続されているため、値が最後に読み込まれた WTI の 78.3 で上書きされています。
これは実務上は問題です。
注意:Dateノードだけにvalueを持たせると値が混ざる
今回のクエリでは、以下のように Date ノードを日付だけでMERGEしています。
MERGE (d:Date {date: date()})
この場合、USDJPY、EURUSD、WTIがすべて同じ日付ノードを共有します。
そのうえで、d.value に価格を入れているため、最後に処理された行の値で上書きされます。
その結果、以下のように全銘柄の値が 78.3 になってしまいました。
"EURUSD" "2026-01-10" 78.3 "USDJPY" "2026-01-10" 78.3 "WTI" "2026-01-10" 78.3
これは「1日1レコード」にはなっていますが、「1銘柄×1日1レコード」にはなっていません。
改善案:DailyPriceノードを使う
1銘柄×1日で価格を管理するなら、以下のように DailyPrice ノードを作る方が分かりやすいです。
LOAD CSV WITH HEADERS FROM 'file:///test.csv' AS row
MERGE (a:Asset {name: row.name})
MERGE (p:DailyPrice {asset: row.name, date: date()})
SET
p.value = toFloat(row.value),
p.source = 'test.csv',
p.updated_at = datetime()
MERGE (a)-[:HAS_DAILY_PRICE]->(p);
確認クエリは以下です。
MATCH (a:Asset)-[:HAS_DAILY_PRICE]->(p:DailyPrice) RETURN a.name, p.date, p.value ORDER BY p.date DESC, a.name;
この形なら、asset と date の組み合わせでノードが分かれるため、銘柄ごとの値が混ざりません。
改善案:制約も作成しておく
DailyPrice に対して、1銘柄×1日で一意になる制約を作成します。
CREATE CONSTRAINT daily_price_unique IF NOT EXISTS FOR (p:DailyPrice) REQUIRE (p.asset, p.date) IS UNIQUE;
これで、同じ銘柄・同じ日付の価格ノードが重複しにくくなります。
今回の到達点
今回の作業で、以下まで確認できました。
- Mac mini上のDockerでNeo4j 5を起動できた
- APOC Coreを有効化できた
- Neo4j BrowserへSSHトンネル経由で接続できた
RETURN apoc.version();でAPOCの動作確認ができたapoc.load.csvは使えないことが分かった- 標準の
LOAD CSVでCSVを読み込めた - CSVから
Assetノードを作成できた - 重複防止の制約を作成できた
- 時系列データを分離する方向性を確認できた
ハマりどころ
Neo4jがRestartingを繰り返す
原因はメモリ設定でした。
Docker Desktopが認識しているメモリ上限を超える設定にすると、Neo4jは以下のエラーで起動できません。
Invalid memory configuration - exceeds physical memory
まずはメモリ指定を外して起動し、必要に応じて後から調整する方が安全です。
apoc.load.csvは使えなかった
今回のAPOC Core環境では、apoc.load.csv は登録されていませんでした。
CSV読み込みは、Neo4j標準の LOAD CSV を使えば問題ありません。
Docker Desktopを起動しないとdocker composeが使えない
MacではDocker Desktopが起動していないと、Docker daemonに接続できません。
電源断や再起動後に以下のエラーが出た場合は、Docker Desktopを起動します。
Cannot connect to the Docker daemon at unix:///Users/snowpool/.docker/run/docker.sock. Is the docker daemon running?
open -a Docker
Dateノードにvalueを持たせると値が混ざる
Date ノードを日付だけで共有し、そこに value を持たせると、複数銘柄の値が混ざります。
価格データは、DailyPrice や Price のような別ノードに分ける方が安全です。
次にやること
今回はCSVを手動で作成して、Neo4jに読み込むところまで確認しました。
次は、実際のデータ取得処理を作ります。
- 為替データの取得
- WTIなどコモディティ価格の取得
- FREDなど経済指標データの取得
- SQLiteからNeo4jへの連携
- Graph RAG用のノード設計
金融危機監視レーダーや購買管理システムで使っているデータを、Neo4j側にも展開していく予定です。
まとめ
Mac mini上のDockerでNeo4j 5 + APOCを起動し、Neo4j Browserへの接続、APOCの確認、CSV読み込みまで実施しました。
最初はメモリ設定が大きすぎてNeo4jが再起動ループになりましたが、メモリ指定をコメントアウトすることで起動できました。
また、apoc.load.csv は使えませんでしたが、標準の LOAD CSV でCSVを読み込めることを確認しました。
今後は、実際の為替・コモディティ・経済指標データを取得し、Neo4jに取り込んでGraph RAGや分析基盤に使える形へ進めていきます。

コメント