サーバーを再起動したら Docker Compose のスタックが上がっていなかった、という経験は珍しくありません。`docker compose up -d` を手動で打つ運用は、再起動のたびに人間の介在が必要になるという構造的な問題を抱えています。この問題を解決するのが、Linux の標準的なサービス管理機構である systemd(システムディー)との統合です。
Kubernetes を使わない選択の合理性
systemd を使った Compose のサービス化を検討するとき、「Kubernetes を使えばよいのでは」という議論が出ることがあります。しかし Kubernetes(複数ノードにまたがるコンテナ群を自動で配置・管理するオーケストレーター)は、マルチノードの自動スケジューリングや水平スケーリングが前提の設計です。内部ツール・ステージング環境・小規模な本番サービスなど、1 台の Linux ホストで完結するワークロードには、Kubernetes の導入コストは見合わないケースが多くあります。
シード記事が挙げる具体的な用途として、Nginx Proxy Manager(リバースプロキシ)、Gitea(自己ホスト型 Git サービス)、Grafana と Prometheus の監視スタック、PostgreSQL と小規模 Web アプリの組み合わせなどがあります。これらは「1 人のエンジニアが 1 つのディレクトリを読めば全体を把握できる」規模のシステムです。その規模であれば、Docker が「コンテナの実行」を担い、Compose が「スタックの定義」を担い、systemd が「ホスト上でのライフサイクル管理」を担う、という役割分担が明快に機能します。
systemd ユニットファイルの仕組み
systemd は、/etc/systemd/system/ 以下に置かれたユニットファイル(拡張子 .service のテキストファイル)を読み込み、プロセスの起動・停止・依存関係の解決を行います。Docker Compose をサービス化するには、このユニットファイルに「Compose プロジェクトをどのように起動し、どのタイミングで起動するか」を記述します。
典型的な構成は次のようになります。
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Stack
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target`After=docker.service` と `Requires=docker.service` により、Docker デーモン(バックグラウンドで常時動作するプロセス)が起動してから Compose スタックを立ち上げる順序を保証します。`Type=oneshot` は「コマンドを実行して終了するタイプのサービス」を意味し、`RemainAfterExit=yes` と組み合わせることで、コマンド終了後もサービスが「実行中」状態として扱われます。これにより `systemctl status myapp` で状態を確認できるようになります。
ユニットファイルを配置したあとは、以下の手順でサービスを有効化します。
sudo systemctl daemon-reload # systemd に新しいユニットファイルを認識させる
sudo systemctl enable myapp # OS 起動時に自動起動するよう登録
sudo systemctl start myapp # 今すぐ起動
sudo systemctl status myapp # 状態確認`daemon-reload` を忘れると、ユニットファイルを編集しても古い定義のまま動き続けるため、変更後は必ず実行します。
プロジェクトディレクトリ構成と運用上の考慮点
シード記事が推奨するディレクトリ構成は、/opt/myapp/ 以下に compose.yaml・.env・data/・backups/ をまとめ、ユニットファイルは /etc/systemd/system/myapp.service に置くという分離です。プロジェクトファイルを /opt に置く理由は、Linux の慣習として「サードパーティのアプリケーション」を置くパスとして確立されているためです。/home 以下に置くと、ユーザー管理の変更や権限の問題が生じやすくなります。
データの永続化(コンテナが停止しても消えないようにすること)は、compose.yaml のボリュームマウントで ./data/ を明示的に指定します。こうするとホスト上の /opt/myapp/data/ にデータが残るため、バックアップも tar や rsync で単純にコピーするだけで済みます。Kubernetes の PersistentVolume とは異なり、どこにデータがあるかを人間が直感的に把握できる点が、この構成の実運用上の強みです。
systemd でサービス化した場合、ログは `journalctl -u myapp -f` で追跡できます。journald(systemd のログ収集コンポーネント)が標準出力をキャプチャするため、別途ログ転送の仕組みを用意しなくても、起動・停止・エラーの記録が一元的に残ります。
アップデート手順についても、シード記事は scripts/update.sh としてスクリプト化することを推奨しています。`docker compose pull` で新しいイメージを取得し、`systemctl restart myapp` でサービスを再起動するという手順を明文化しておくことで、深夜のインシデント対応時でも迷わず操作できます。
整理すると、この構成が提供するのは次の特性です。
- OS 起動時の自動起動(enable で登録)
- シャットダウン時の正常終了(ExecStop で down を実行)
- 依存関係の順序保証(Docker デーモンの起動後に Compose を起動)
- ログの一元管理(journald 経由で journalctl が使える)
- 状態確認の統一(systemctl status で他のサービスと同じ操作感)
Kubernetes や Nomad(HashiCorp 製のワークロードオーケストレーター)への移行が視野に入った時点で、この構成は廃止の対象になります。しかしそれまでの間、Compose と systemd の組み合わせは「1 人でも把握・修復できる」インフラとして、内部ツールや小規模サービスの実運用に十分応えられます。サービス管理の複雑さを必要最低限に抑えながら、自動化と再現性を確保するという設計の意図は、長く稼働するシステムの保守コストに直結します。