前回、画面もキーボードもない箱に、別の部屋からSSH(ネットワーク越しにコマンドで操作する仕組み)で話しかけたら返事が返ってきた、というところまで書いた。
今回はその箱に、初めて自分のウェブサイトを乗せる。中身はHTMLファイル1枚、「自宅サーバー、稼働中。」とだけ書かれた、ほぼ空のページだ。目的はページの中身ではない。「公開して、壊れたら直して、また動かす」という一連の運用のサイクルを、自分の手で一度通しきることだ。
空っぽのHTMLを、Dockerの箱に詰める
まず、そのHTML1枚をnginx(エヌジンエックス、Webサーバーのソフトウェア)に読み込ませて、Docker(アプリケーションを「箱」に入れて動かす仕組み)のコンテナとして立ち上げた。
設定はcompose.yamlという1つのファイルに書く。ここに「どのイメージを使うか」「どのポートを使うか」「再起動時にどうするか」といった構成をまとめて書いておけるので、あとで見返しても自分が何をしたか分かる。設定をコマンドの打ち捨てにせず、ファイルとして残せる点がありがたかった。
書いていて引っかかったのが「8080:80」という記述だった。数字が2つ並んでいて、最初は何が違うのか分からなかった。調べて腑に落ちたのは、コロンの右側(80)は「コンテナの中でnginxが待ち構えている、Webの世界共通の標準番号」で、これは変えられない。コロンの左側(8080)は「そのPCの外から見たときに、どの窓口番号でアクセスするか」で、こちらは自分で好きな番号を決めていい。「コンテナの中の決まりごと」と「外から見た自分の窓口」が別物だと分かったら、急にすっきりした。
docker compose up -dと打ったら、箱が起動した。手元のPCからhttp://<箱のIPアドレス>:8080にアクセスすると、「自宅サーバー、稼働中。」の文字が表示された。まだ自宅のネットワークの中だけの話だが、初めて自分の手でWebサーバーを立てて表示させた瞬間で、地味に嬉しかった。
わざと電源を落として、生き返るか試した
compose.yamlにはrestart: alwaysという一行も書いておいた。停電やPCの再起動があっても、コンテナが勝手に立ち上がり直す設定だ。
これが本当に効くのか、自分の目で確かめたくなった。サーバーの電源を落として、PCごと再起動させてみた。
1分ほど待ってからSSHで入り直し、docker ps(動いているコンテナの一覧を見るコマンド)を打った。誰も何も操作していないのに、STATUSの欄に「Up 57 seconds」と出ていた。CREATED(作られた時刻)を見ると8分前になっている。つまり、コンテナ自体は8分前に作られたものと同じだが、PCが再起動してから57秒で自分の意思とは関係なく勝手に立ち上がっていた、ということだ。
「電源が落ちても勝手に復活するサーバー」が、目の前で本当にできていた。指示を出していないのに動き出している様子を見るのは、思っていたより不思議な感覚だった。
ルーターに穴を開けずに、世界に公開する
自宅の中だけで見えても仕方がない。次は、家の外からもこのページが見えるようにする番だ。
普通に考えると、ルーターの設定を開いて「ポート開放」という作業が必要になる。ただ、これは自宅のネットワークに外から直接届く入り口を開けることでもあり、初心者がいきなりやるには少し怖い。
そこで今回は、Cloudflareという会社が無料で提供している「Quick Tunnel(クイックトンネル)」というお試し機能を使った。アカウント登録すら不要で、cloudflaredというコマンドラインのソフトを入れて、
cloudflared tunnel --url http://localhost:8080
と打つだけでいい。するとtrycloudflare.comで終わる、ランダムな文字列のURLが1つ発行される。このURLにアクセスすると、Cloudflareが自宅のサーバーまで裏側でトンネルを通して、ページを取ってきて表示してくれる。ルーターの設定は一切触っていない。
出てきたURLを、Wi-Fiを切ったスマホ(モバイル回線)で開いてみた。「自宅サーバー、稼働中。」の文字が、ちゃんと表示された。
自宅の3万円の中古PCに置いたページが、Wi-Fiを介さないモバイル回線から見えている。家のルーターには穴ひとつ開けていない。これがここまでの作業の中で、一番感動した瞬間だった。仕組みは分かっていたつもりだったが、実際にスマホの画面で見るまでは、どこかで信じきれていなかったのだと思う。
ログの「ERR」に一瞬ひやりとした
作業を終えてcloudflaredをCtrl+Cで止めたとき、ログにERRという文字が流れた。一瞬、何か壊したかとひやりとした。
落ち着いて時刻を見返すと、そのERRは自分がCtrl+Cを押した「後」に出ていた。トンネルを閉じる過程で出る、店じまい中の悲鳴のようなもので、実害はなかった。ここで一つ学んだのは、「ERRという文字が出た=即座に問題」ではなく、「自分がいつ何をした直後に出たか」という前後関係で読む必要がある、ということだ。ログは時系列の記録であって、警報ではない。
余談だが、同じログの中にnrt16という文字列があった。調べてみると、Cloudflareが世界中に持っている拠点のコードで、nrtは成田(Cloudflareの東京拠点)を指すらしい。自分の家のサーバーが、東京の拠点を経由して世界と繋がっていたのだと分かって、少し面白かった。
GitHubに置いたら、1分後に反映されるようになった
次に、ページの中身を書き換えるたびに手作業でファイルをサーバーに送り直すのをやめたかった。GitHub(ソースコードを保存・共有できるサービス)にリポジトリを作り、そこにHTMLファイルを置いた。
サーバー側では、1分おきに「GitHubに新しい変更がないか確認して、あれば取り込んで反映する」というスクリプト(deploy.sh)を、cron(決まった時間ごとに処理を自動実行する仕組み)で回すようにした。
試しに、GitHubのWeb画面上でHTMLの文字を1行だけ書き換えてみた。1分ほど待ってから、家の中のアドレス(http://<箱のIPアドレス>:8080)で開き直すと、自宅サーバーのページがちゃんと書き換わっていた。自分は「編集して保存した」以外に何もしていない。これがGitOps(GitHubへの変更を起点にして自動で反映させるやり方)と呼ばれるものらしい、と後から知った。
古いページの幽霊に惑わされた
ここで一つ、ハマった。
最初、Dockerの設定でHTMLファイルを「1個だけ」コンテナの中にマウント(差し込む)していた。ところが、この反映テストで書き換えたはずのページを開いても、古いページが表示され続けた。ファイルの中身はGitHub側でもう書き換わっているはずなのに、表示だけ古いままという、奇妙な状態だった。
調べて分かったのは、Linuxの世界では「ファイル名」と「ファイルの実体(inode)」が別物だということだった。git pull(GitHubの最新の内容を手元に取り込むコマンド)でファイルが更新されるとき、実際には同じ名前の新しい実体が作られて置き換わる。ところが「ファイル1個だけ」を指定してマウントしていたDockerは、最初につかんだ古い実体を握ったままで、名前が同じでも新しい実体には気づかない。中身はコンテナの外でとっくに書き換わっているのに、コンテナの中だけ古いページの幽霊を配り続けていた。
直し方は、公開用のpublicフォルダを作って、ファイル1個ではなく「フォルダごと」マウントする形に変えることだった。フォルダ単位なら、中の実体が入れ替わってもDockerはちゃんと追いかけてくれる。直してからは、GitHubを書き換えるたびに問題なく反映されるようになった。「名前が同じでも中身の実体は別物」という、Linuxの仕組みの一端に触れた出来事だった。
気づいたら「家の外」のコピーはもうできていた
最後に、バックアップの話も片付けておく。よく言われる「3-2-1」という考え方がある。データを3つコピーして、2種類以上の異なる場所に置き、そのうち1つは家の外に置く、というルールだ。
改めて整理してみると、GitHubにファイルを置いた時点で、実はもう「家の外の1コピー」が済んでいたことに気づいた。GitHub自体がクラウド上、つまり自宅の外にある。意識してやったわけではなく、GitOpsの仕組みを作った副産物として、3-2-1の一角がすでに埋まっていた。
これに加えて、週に1回、サーバー内のデータをまとめて別の場所にコピーする処理もcronに登録しておいた。これで最低限の型はできたと思う。
空っぽのサイトで、一周した実感
サイトの中身は最後まで空っぽのままだった。それでも、コンテナ化して、壊れても自動で復活する仕組みを作って、ルーターに穴を開けずに安全に公開して、GitHubに置くだけで自動で反映される仕組みを作って、最後にバックアップまで整えた。この一連の型を、自宅の小さな中古PCの上で、初めて自分の手で最後まで一周できた。
途中で2回ほど詰まった(幽霊ページの件と、最初のERRのひやり)が、どちらも「何が起きているか」を落ち着いて調べたら、ちゃんと理由があった。分からないことに出会うたびに少しずつ減っていく、あの感覚が今回もあった。
次は、この箱にClaude Codeを常駐させて、スマホから話しかけたら作業してくれる構成を作っていく。最初にXでこの連載のきっかけになった投稿を見たとき、憧れていたところにいよいよ近づく。