デプロイを「操作」から「成果物」に変えた話
4 min read

コードが残っていればすぐ動く。そう思っていた。
――しかし実際には、その時点で動いていたアプリケーションを、後から同じ手順でビルドし直すのは思った以上に大変である。
当時ハマったポイントや、試行錯誤の記憶は必ずしもすべて残っているとも限らない。 結果だけが残っている状態になる。
つい先日、Rails 7.0のアプリケーションを0から構築し直してみた。予想以上に大変だった。1
今では当たり前のように用意されているDockerfileも、この当時は自分で書いていた。 その時点では確かに動いていたはずなのに、その状態に戻すことは簡単ではなかった。
Kamalでデプロイしていたとしても、コンテナイメージそのものを残していなければ、そのときの状態は失われてしまう。 結果として、当時の知識や文脈を思い出すところからやり直すことになる。
そこで最近は、デプロイの考え方を少し変えている。
デプロイを「操作」ではなく、「保存された成果物の選択」として扱う構成にした。
具体的には、次のような形である。
- GitLabでDockerイメージをビルドし、レジストリにプッシュする
- Harborでそのイメージをレプリケーションし、別環境に保存する
- Portainerから、その保存されたイメージを選択してデプロイする
GitLabはあくまでビルドの場であり、長期的な保存は担わない。 Harborはレプリケーションによってイメージを残す。いわばタイムマシンのような位置づけである。 Portainerは、その中からどのイメージを使うかをUI経由で選択する。
この構成にすると、デプロイは「作る行為」ではなく「選ぶ行為」になる。
すでに存在しているイメージを使うだけなので、手元環境に依存しない。 また、過去の状態をそのまま再現できる可能性が高くなる。
もちろん、この方法にも欠点はある。
イテレーションは遅くなるし、すべてが自動化されているわけでもない。 Portainerのテンプレート(templates.json)にも独特の癖があり、扱いやすいとは言い難い。
それでも、「動いていた状態を残せる」という点は大きい。
より正確には、「動いていた状態を残す」という意識を持てるようになったことが大きい。
コンテナイメージは決して軽くない。
Railsの標準的な構成でも、Alpineなどを使わなければ1GB前後になることもある。 これまでは、そのようなものを長期的に保存することに価値を見出していなかった。
GitLabのバックアップも、コードを最優先とし、コンテナイメージのような容量を消費するものは積極的に残してこなかった。
しかし、それは誤りだったと思っている。
過去のイメージは脆弱性が報告されたライブラリが含まれているかもしれない。 それでも、それは「その時点で確実に動いていた状態」であり、自分が試行錯誤の末に到達した成果物でもある。
コードだけでなく、「動く状態」そのものにも保存する価値はある。
Kamalは開発と密接に結びついたツールであり、イテレーションを高速に回すには非常に適している。 一方で、この構成は開発から少し距離を置き、「安定した状態を扱う」ことに向いている。
どちらが良いという話ではなく、性質が異なる。
Goのように単体バイナリで完結する言語では、わざわざDockerfileにする意味もないと思う。2
しかしRailsのように環境依存が強いアプリケーションでは、「動く状態」をそのまま保存することは難しい。 Dockerはその問題を解決する手段として機能する。
これまでコードはgitに残してきたが、これからは「動く状態」も残していきたいと考えている。
Webアプリケーションは本質的には動的なものだが、それでも再現可能な形で保存しておくことには意味がある。
今回の投稿は万人に対するおすすめではないものの、セルフホストやhomelabのような環境で自分のコードを運用そのものを楽しめる人には共感してもらえると思う。
デプロイを「操作」ではなく「成果物」として捉える視点は、一度考えてみてもよいはずだ。