古いノートPCをDebianの開発サーバにして、Macを「薄い端末」にした

机の脇に、Windowsを入れたまま使わなくなったノートPCが1台転がっていました。第8世代のCore i7に16GB、性能としては今のMacに敵うはずもなく、かといって捨てるには惜しい。この「中途半端に元気な古いマシン」を、開発の常駐サーバにしてしまおう、というのが今回の話です。

きっかけは、もっと日常的な不満でした。開発中のプロジェクトが増えると、Macの中に複数のDockerスタックが積み上がり、ファンが回り、~/.claude のような作業の状態がMacごとにバラつく。2台のMacを使い分けていると「あの設定、どっちに入れたっけ」が地味に効いてきます。

そこで発想を変えました。開発の実体をサーバ1台に集め、Macは「SSHで入るだけの薄い端末」にしてしまえばいい。そうすれば重い処理はサーバが背負い、Macは軽いまま、状態はサーバに1つだけ。この記事は、その最小構成を実際に組んで、既存のコンテナを1本移設し終えるまでの実体験の記録です。「作りました」という成功譚ではなく、途中で取り違えかけた前提や、正直な向き不向きも含めて書きます。

対象読者はこんな方です。

  • 使っていない古いPCを開発サーバに転用したい人
  • MacのDocker常駐を軽くしたい人
  • 「自宅サーバ」に興味はあるが、どこまでやれば実用になるのか知りたい人

前提の実機:

  • ノート = i7-8565U(Whiskey Lake / 4コア8スレッド / TDP 15W / GPUなし)/ 16GB
  • OS = Debian 13「trixie」headless
  • 母艦 = MacBook(Apple Silicon)

まず設計を1行に絞る

手を動かす前に、ゴールを1行に削りました。「コンテナが動く、かつMacからSSHでサーバのファイルを直接編集できる」。これだけです。

正直に言うと、最初はもっと欲張っていました。サーバにClaude Codeを常駐させて2台のMacで記憶を共有し、さらに自前のRAG検索まで乗せる——そんな構想を書いていました。ですが、詰めていくうちに大半は「今やる必要のない過剰投資」だと分かって、削りました。欲張った設計をそのまま実装に持ち込むと、動かないものを抱えたまま身動きが取れなくなります。ここははっきり書いておきたいところで、サーバ構築でいちばん効いたのは技術ではなく「スコープを縮めた」判断でした。

削った結果、「記憶を2台で同期する」という悩み自体も消えました。サーバに実体を1つ置けば、同期という問題がそもそも発生しない。Macは同じサーバの同じプロセス・同じファイルを覗くだけなので、どちらのMacから入っても同じ状態が見えます。同期ツールも、コピーも、競合もいらない。「同期をどうするか」ではなく「同期をなくす」に倒したわけです。


Debianを最小で入れる

土台はDebian 13「trixie」のnetinstを使いました。古いx86ノートを24時間動かすための部品は、どれも枯れた標準手順で揃います。

インストールで唯一こだわったのは、ソフトウェアの選択でデスクトップ環境を全部外し、「SSH server」と「standard system utilities」だけを選ぶこと。ここでSSH serverを明示的に選び損ねると、画面のないサーバにログインする手段がなく、あとから詰みます。最初だけはモニタとキーボードを繋いで作業しました。

OSが上がったら、Mac側から鍵を送ってSSHで入れるようにします。

# Mac側: 鍵が無ければ作り、サーバへ公開鍵を送る
ls ~/.ssh/id_ed25519.pub || ssh-keygen -t ed25519
ssh-copy-id <user>@<server-ip>
ssh <user>@<server-ip>   # パスワード無しで入れることを確認

鍵ログインが通ったことを確認してから、パスワード認証を無効化します(順番が逆だと自分を締め出します)。ここから先はMacのターミナルから全部やれるので、ノートの蓋は開けて立て置きにし、モニタとキーボードは片付けました。

Dockerは、Debian同梱の docker.io ではなく公式aptリポジトリから入れます。同梱版は少し古く、compose pluginの挙動も新しい方が素直です。

sudo apt -y install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/debian $(. /etc/os-release; echo $VERSION_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER   # sudo無しでdockerを使う(再ログインで反映)

docker run --rm hello-world が通れば土台は完成です。ここまでは特にハマりどころもなく、拍子抜けするくらい素直に進みました。

「AIとコンテナを動かしたら、この非力なノートが重くなるのでは?」と最初は身構えていました。でもこれは誤解でした。Claude CodeのAI(言語モデル本体)はAnthropicのクラウドで動いていて、手元で動く claude はAPIを叩くだけの軽いNodeプロセスです。だから「AIを動かすからサーバが重い」は当たりません。ローカルで実際に重いのは、フロントのdevサーバやビルド、そして同時に立てるコンテナの数×メモリです。


既存コンテナを「Macを壊さずに」移す

土台ができたら、次は今動いているものを1本だけサーバへ移します。ここが一番緊張する工程です。データを移すからには、失敗してもMac側が無傷であることを絶対条件にしました。

手順で選んだのは、Postgresの論理ダンプ(pg_dump)です。ポイントは**「Macの稼働中DBを読むだけ」で、元のコンテナもボリュームも一切触らない**こと。サーバ側で完全に動いて検証が通るまで、正本はずっとMacに置いておきます。

# Mac側: 稼働中のコンテナから論理ダンプを取る(読むだけ)
docker compose exec -T postgres pg_dump -U postgres -d <db> \
  --no-owner --clean --if-exists > /tmp/dump.sql
scp /tmp/dump.sql <server>:/tmp/dump.sql

サーバ側では、先にDBコンテナだけ起動して、healthyになるのを待ってから流し込みます。

# サーバ側: DBだけ先に上げて、readyを待ってからrestore
docker compose up -d postgres
until docker compose exec -T postgres pg_isready -U postgres; do sleep 1; done
cat /tmp/dump.sql | docker compose exec -T postgres psql -U postgres -d <db>
docker compose up -d   # 残りのサービスも起動

復元先は必ず同じPostgresのメジャーバージョンに合わせます(16なら16)。バージョンが違うとrestoreがこけます。そして失敗したときの逃げ道を先に用意しておくこと。サーバ側は docker compose down -v でボリュームごと捨ててやり直せば、Macは無傷なので何度でもやり直せます。この「いつでも捨てられる」という安心感があると、移設作業の心理的な重さがまるで違います。

検証は、MacとサーバでDBの主要テーブルの件数を突き合わせるだけ。数字が一致すれば成功です。実際にやってみると、自分のOSS(DBが1本だけの小さいもの)は全テーブルの行数がぴたりと一致しました。続けて、レガシーなPHPで動く一回り厄介なプロジェクトも移してみましたが、こちらもamd64向けのビルドが通ってコンテナ一式が立ち上がり、ブラウザからのアクセスでちゃんとページがレンダリングされました。サイズのあるDBでも問題なく復元でき、この手順が小さなOSS以外でも通用することを確認できています。

ここで大事なのは、成功してもすぐにMac側を止めないこと。しばらくは両方を残し、サーバ側が安定していることを日をまたいで確認してから、Mac側の停止を判断しました。急いで正本を移し替えて得することは何もありません。


port競合は「ポートを公開しない」で消える

プロジェクトが1本のうちは何も起きませんが、複数を同時に立てようとした瞬間、誰もが 5432 の取り合い(Postgresのポート衝突)にぶつかります。ここで私が取り違えかけたのは「ポートをずらして増やす」方向でした。答えは逆で、ホストにポートを公開するのはリバースプロキシ1個だけにします。

考えてみれば当たり前で、サービス同士の通信は同じcomposeネットワークの中でサービス名解決できるので、ホストのポートなんて要りません。ホストのポートが要るのは「Macから触る入口」だけ。だからDBを含めて他は ports: を書かず、80/443を持つのはプロキシ(Traefikなど)1個に集約し、ホスト名でプロジェクトを振り分ける。こうすれば、プロジェクトを何本立てても衝突しません。

そして本当の上限は、ポートではなくメモリとCPUでした。16GBあれば基盤で1〜2GB、残りをプロジェクトに回して、アイドル状態のdevサーバなら6〜8本は積めます。ただし複数を同時にフルビルドすると、15Wの4コアはあっさり頭打ちになります。快適に同時稼働できるのは2〜4本、というのが実機で触った感触です。


やってみて分かった、正直な向き不向き

ここははっきり書いておきたいところです。このノート(i7-8565U)は、MacBookのM2 Proより遅い。 重いビルドは、素直にMacでやった方が速いです。

だから、サーバに寄せて得られるものを取り違えないことが大事でした。得られるのは、

  • 常駐: 電源を入れっぱなしにできる、いつでもそこにいるハブ
  • 集約: 開発の状態(コンテナ・DB・設定)が1か所にまとまる正本
  • 端末の使い捨て: どのMacから入っても同じ環境。Macは薄くて構わない

——であって、ビルド速度ではありません。「全プロジェクトを同時に重くビルドして速くする」ような使い方は、この15Wチップには向いていません。アイドルで並べておいて、重いビルドは1つずつ、が身の丈に合っています。

体感の話も1つ。SSH越しの編集が重いのでは、と心配していましたが、編集のもたつきを決めるのは帯域ではなく往復の遅延(レイテンシ)で、同じLAN内なら1ミリ秒前後。キー入力も画面の差分も軽いので、リモート編集で困る場面はありませんでした。むしろ有線(Gigabit Ethernet)でサーバを繋いでおくと、git clonedocker pull のような大きな転送のときに効きます。

古いノートを24時間動かす都合で、ハード側の設定もいくつか。BIOSで「AC Power Recovery(停電復帰)」を有効にしておかないと、停電のあとに落ちたまま起きてきません。蓋を閉じてもスリープしないようにし、電池の膨張・発火が怖いので充電の上限を絞る(機種によっては物理的に外してAC直結)。排熱のために蓋は開けて立て置き。このあたりは技術というより「無人で置きっぱなしにする物」への配慮で、地味ですが省けません。


まとめ

古いノートPCの開発サーバ化で一番効いたのは、意外にも手順ではなく**「何をやらないか」を先に決めたこと**でした。常駐Claudeも自前RAGも一旦やめて、「コンテナが動く+SSHで編集できる」の一点に絞る。そこまで削ったら、Debianの最小構築も、Macを壊さない移設も、port競合の解消も、すべて枯れた標準手順の組み合わせで淡々と片づきました。

きれいごとに聞こえるかもしれませんが、非力な古いマシンでも「常駐・集約・端末の使い捨て」という役割に割り切れば、十分に現役です。ビルドの速さを期待して同じことをすると、たぶんがっかりします。そこだけ取り違えなければ、机の隅の古いノートは、想像以上に頼れる相棒になりました。

「うちにも使っていない古いPCがある」という方は、まず捨てないで、最小のサーバにしてみるところから始めてみてください。

なお、このサーバの上で動かしているエージェント常駐の話や、日々の開発の中身については別の記事で書いています。あわせてどうぞ。

Claude Codeで11人のエージェント組織を作った話

サーバに集約した開発環境の上で、複数エージェントをどう動かしているかを実体験ベースで書いた記事です

/blog/claude-code-multi-agent-team/

Webカメラだけで物理マウスを捨てるOSSを自分用にチューニングした話

「OSSを動かす」と「自分の環境に合わせ込む」は別物、という同じ手触りのパーソナルハック記録です

/blog/nonmouse-m2-python314-tuning/

関連記事