古いノート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 clone や docker 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/
関連記事
- 技術
REST API vs GraphQL: 開発規模ごとの選択基準
REST APIとGraphQLの選択は技術の優劣ではなく、チーム規模とデータ要件の文脈で決まる。小規模・中規模・大規模それぞれの判断基準と、失敗事例から得た実践的な意思決定フレームワークを解説する。
GraphQLAPIアーキテクチャ - 技術
Feature-Sliced Design と Packaged by Feature: スケーラブルなアーキテクチャの設計
コードが増えるほど開発が遅くなる原因はファイル整理にある。フロントエンドの Feature-Sliced Design とバックエンドの Packaged by Feature を、3層構造やマイクロサービス分割の実例とともに解説する。
アーキテクチャフロントエンドTypeScript - 技術
1チケットを自律で消し切るループを作った――司令塔1・マネージャ1・専任3の敵対的レビュー付き設計
Claude Codeで課題トラッカーのチケットを「完了相当」まで自律処理するコマンドを設計・実装した記録です。司令塔1・マネージャ1・専任3体の3層構成、TC作成から実装・レビュー・PRまでのパイプライン、逐次と確認の扱い、そして多エージェントの敵対的レビューで設計を詰めた過程を、正直な未検証点も含めて共有します。
ClaudeAIマルチエージェント
