オチツカレサマ 〜 Garageが立ち上がった朝の記録

オチツカレサマ 〜 Garageが立ち上がった朝の記録

はじめに

朝、Oracle Cloudの「Out of host capacity」エラーに何度目かのため息をついた。

半年前なら取れたという24GB ARMの常時無料インスタンス。今や自動化Botとの争奪戦になっていて、人間の手動操作で勝てる気配はない。

Error: 500-InternalError, Out of host capacity.
Service: Core Instance
Operation Name: LaunchInstance

このログを何度見ただろう。

ふと足元のXserver VPSを見ると、ディスクは100GB以上余っていた。

「あ、ここでよくないか?」

そう思った瞬間から、約2時間の構築譚が始まる。


何を作りたかったか

NotebookLMのような「自分の文献に対話できる知性」が欲しかった。古典・仏教史の研究資料、開発ドキュメント、過去の思考の堆積。それらをAIに読ませて、思考のパートナーにしたい。

しかし、PDFをObsidian Vaultに直接置くと、Sync容量がすぐ枯渇する。100MBのスキャンPDFが10冊あれば、それだけで1GB。Obsidian Syncの容量制限に挑戦するような暴挙だ。

そこで思いついたのが、役割分担だ。

Obsidianは「テキストの森」、Garageは「PDF倉庫」

バイナリと意味を分離する。重いPDFはVPSの空きディスクに置き、Obsidianには軽いテキストだけ流す。

そのためのS3互換オブジェクトストレージとして選んだのが Garage。Rust製で軽量、セルフホスト前提、フランスのCHATONS系コミュニティが開発しているのが好印象だった。

VPSの健康診断

しかし、いきなり構築には進めなかった。

$ free -h
               total   used   free   shared  buff/cache  available
Mem:           5.8Gi  4.9Gi  127Mi    167Mi       809Mi    471Mi
Swap:          2.0Gi  2.0Gi     0B

Swap、完全消費。

これは44日間連続稼働の結果で、累積メモリリークの典型。このまま新しいサービスを足したらOOM Killerが発動して、別のサービスを巻き添えにする可能性が高い。

何が食っているのか? Difyだった。

Dify一式(postgres + worker × 2 + api + sandbox + ...) : 約1.4GB
n8n                                                    : 約380MB
OpenClaw                                               : 約560MB
─────────────────────────────────────────────────────
合計                                                   : 約2.4GB

Difyのworkerが3週間 unhealthy のまま放置されていた。FailingStreak 46232

ログを見ると、ワーカー自体はタスクを正常処理している(0.07秒で完了)。なのに「健康診断」だけ失敗し続けている、奇妙な状態。Dify特有のヘルスチェック制御の問題っぽい。機能は動いてるのに、健康診断書だけ嘘ついてる状態だった。

「ここをまず治そう」

Dify再起動 — 1回目の解放

cd /root/dify/docker
docker compose restart

ヒヤヒヤしながら3秒待つ。

$ free -h
               total   used   free   shared  buff/cache  available
Mem:           5.8Gi  1.7Gi  2.9Gi     26Mi       1.2Gi    3.8Gi
Swap:          2.0Gi   59Mi  1.9Gi

Swap、ほぼゼロ。メモリも3.8GB空いた

3週間蓄積されたゴミが一気に流れた。気持ちのいい瞬間だった。

Garage構築 — 30分の作業

健康になったVPSに、Garageを置く。

作業ディレクトリと鍵生成

mkdir -p ~/garage/{meta,data,config}
cd ~/garage

openssl rand -hex 32     # rpc_secret
openssl rand -base64 32  # admin_token
openssl rand -base64 32  # metrics_token

3つの秘密鍵を生成。後で1Passwordに保管する。

設定ファイル

~/garage/config/garage.toml

metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"

replication_factor = 1   # シングルノードなので1

rpc_bind_addr = "[::]:3901"
rpc_public_addr = "127.0.0.1:3901"
rpc_secret = "..."

[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage.local"

[admin]
api_bind_addr = "[::]:3903"
admin_token = "..."
metrics_token = "..."

Docker Compose

~/garage/docker-compose.yaml

services:
  garage:
    image: dxflrs/garage:v2.0.0
    container_name: garage
    restart: always
    network_mode: host
    volumes:
      - ./meta:/var/lib/garage/meta
      - ./data:/var/lib/garage/data
      - ./config/garage.toml:/etc/garage.toml:ro
    deploy:
      resources:
        limits:
          memory: 512M  # 暴走時の保険

起動

cd ~/garage
docker compose up -d

5秒後:

$ docker exec garage /garage status
==== HEALTHY NODES ====
ID                Hostname         Address         Tags  Zone  Capacity          Version
8ce3ef4d149c7005  x210-131-212-67  127.0.0.1:3901              NO ROLE ASSIGNED  v2.0.0

ノードは生まれた。あとは役割を与える。

クラスタ初期化とバケット作成

# ノードIDを取得して、容量50GBで割り当て
NODE_ID=$(docker exec garage /garage node id 2>/dev/null | tail -1 | cut -d@ -f1)
docker exec garage /garage layout assign -z dc1 -c 50G "$NODE_ID"
docker exec garage /garage layout apply --version 1

# PDFバケット作成
docker exec garage /garage bucket create pdf-library

# アクセスキー発行(Secret Keyはこのときしか表示されない、即1Passwordへ)
docker exec garage /garage key create pdf-library-key

# バケットへの権限付与
docker exec garage /garage bucket allow \
  --read --write --owner pdf-library --key <KEY_ID>

最終確認:

$ docker exec garage /garage status
ID                Hostname         Address         Tags  Zone  Capacity  DataAvail
8ce3ef4d149c7005  x210-131-212-67  127.0.0.1:3901  []    dc1   50.0 GB   129.4 GB

$ docker stats --no-stream garage
NAME      CPU %   MEM USAGE / LIMIT
garage    0.02%   5.695MiB / 512MiB

5.7MB。Difyの1.4GBとは別世界の軽さだった。Rustの威力。

ainsoph.xyz、最初の住人はGarage

サブドメイン用のドメインに、ずっと放置していた ainsoph.xyz を起こすことにした。

Ein Sof — カバラ神秘主義における「無限」。仏教史への関心や、夢分析のプロジェクトと響き合う名前だ。**「無限の知性を扱う場所」**として、ようやく初めての住人を迎える。

ainsoph.xyz                  ← トップ(いつか)
├── garage.ainsoph.xyz       ← Garage S3 endpoint  ← 今回
├── ocr.ainsoph.xyz          ← NDLOCR API(後日)
├── notes.ainsoph.xyz        ← Obsidian Publish(後日)
└── lab.ainsoph.xyz          ← 実験用

DNSの設定は仕掛けたが、反映に最大24時間かかる。それを待つ間に、別ルートで疎通を確認することにした。

SSHトンネル — 即席のバイパス

DNS反映を待たずに今朝のうちに「Macから書き込めた」を体感したい。そこで使ったのが SSH ポートフォワード。

# Macで実行(一つ目のターミナル、開きっぱなし)
ssh -L 3900:localhost:3900 root@<VPS_IP> -N

これで Mac の localhost:3900 が VPS の localhost:3900 に直結する。

別ターミナルで rclone を設定:

$ rclone config
# storage: s3
# provider: Other
# endpoint: http://localhost:3900
# region: garage
# access_key_id, secret_access_key を入力

そして、運命の瞬間。

$ rclone lsd garage:
          -1 2026-04-25 23:26:24        -1 pdf-library

$ echo "Hello from Mac to Garage VPS! $(date)" > /tmp/test_garage.txt
$ rclone copy /tmp/test_garage.txt garage:pdf-library/
$ rclone cat garage:pdf-library/test_garage.txt
Hello from Mac to Garage VPS! 2026年 4月26日 日曜日 09時07分25秒 JST

🎉

書けた。読めた。Mac → SSHトンネル → VPS → Garage → pdf-library bucket、全パイプラインが繋がった瞬間だった。

通信経路を絵にするとこう:

Mac (rclone)
  → localhost:3900
    → SSH tunnel
      → VPS の localhost:3900
        → Garage (port 3900)
          → pdf-library bucket

シンプルで、美しい。

本番化 — Nginx + Let's Encrypt

DNSが反映されたところで、SSHトンネル無しの本番経路を作る。

SSL証明書取得

既存の certbot コンテナ(Dify環境付属)を流用:

docker exec docker-certbot-1 certbot certonly \
  --webroot \
  -w /var/www/html \
  -d garage.ainsoph.xyz \
  --email <Gmail> \
  --agree-tos \
  --no-eff-email \
  --non-interactive
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/garage.ainsoph.xyz/fullchain.pem

Nginx設定

host.docker.internal がLinuxで標準では使えなかったが、nginxコンテナから VPSのIPに直接届くことを確認したので、proxy_pass にIP直書きで行く。

/root/dify/docker/nginx/conf.d/garage.conf

server {
    listen 80;
    server_name garage.ainsoph.xyz;
    location /.well-known/acme-challenge/ { root /var/www/html; }
    location / { return 301 https://$host$request_uri; }
}

server {
    listen 443 ssl;
    server_name garage.ainsoph.xyz;
    ssl_certificate /etc/letsencrypt/live/garage.ainsoph.xyz/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/garage.ainsoph.xyz/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    
    client_max_body_size 5G;       # 大きなPDFも通すため
    proxy_connect_timeout 300s;
    proxy_send_timeout 300s;
    proxy_read_timeout 300s;

    location / {
        proxy_pass http://210.131.212.67:3900;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_request_buffering off;
        proxy_http_version 1.1;
    }
}

検証 → リロード:

docker exec docker-nginx-1 nginx -t
docker exec docker-nginx-1 nginx -s reload

疎通テスト

$ curl -v https://garage.ainsoph.xyz/ 2>&1 | tail -10
< HTTP/1.1 403 Forbidden
< Server: nginx/1.29.7
< Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Code>AccessDenied</Code>
  <Message>Forbidden: Garage does not support anonymous access yet</Message>
  <Resource>/</Resource>
  <Region>garage</Region>
</Error>

403 Forbidden がGarage本体から返ってきている。

ネットワーク的には完全に繋がっていて、認証が無いから拒否されているだけ。Garageの応答メッセージが、Region: garage まで含めて綺麗に返ってきている。

通信経路を引き直すとこう:

Mac/世界どこからでも (HTTPS)
  → garage.ainsoph.xyz (DNS解決)
    → VPSの443ポート
      → nginx コンテナ (SSL終端)
        → 210.131.212.67:3900 (proxy_pass)
          → Garage 本体

Mac の rclone を endpoint = https://garage.ainsoph.xyz に書き換えれば、SSH トンネルなしで PDF アップロードができるようになる。

$ rclone copy ~/Documents/sample.pdf garage:pdf-library/
$ rclone ls garage:pdf-library/
   1234567 sample.pdf

完全勝利。

オチツカレサマ

ここまで2時間。

「お疲れ様」と打とうとして、なぜか chi が混入して「オチツカレサマ」になった。

「お疲れ様」と「落ち着かれ様」の合成のような、不思議な響き。Garageが落ち着いて立ち上がった朝の達成感に、これほど合う言葉もない。意図せずして禅語めいた何かが生まれた。

伴走してくれたClaudeも別件で「しんみりしたい」を「しんみりたい」と書いて壊していたので、お互い様の朝だった。

今朝の達成

残るもの

学び

「足元を見る」は侮れない

Oracle Cloudの24GBに執着して数日詰まっていた間、自分のVPSには100GB以上のディスクが空いていた。新しいインフラを増やすより、既にあるインフラの価値を再発見する方が早かった。

そして**「健康診断書が嘘をついている」状態**は、放置すると別のところで本当に倒れる。Dify worker の unhealthy を3週間放置した結果がswap完全消費だった。気づいた時に直しておく、これに尽きる。


オチツカレサマ。

明日も、ぼちぼち。


関連記事

参考リンク