はじめに
サーバーを再起動したら、docker compose up -d で立てたコンテナが全部消えていた——一度はやるミス。
# 再起動前は動いていた
docker compose up -d
# サーバー再起動後…
docker ps
# 何も出ない
Docker デーモン自体は起動時に戻ってくる。ではなぜコンテナは戻らないのか。
👉 docker compose up は「今このコンテナを起動する」という一度きりの指示。ホストを再起動すると、その指示は残っていない。
--restart を付ければいいと思うかもしれない:
# compose.yaml
services:
app:
image: myapp
restart: always
これは Docker デーモンが再起動したときにはコンテナを復帰させる。しかし:
❌ restart: always だけでは足りない場面がある
- compose ファイル一式(複数サービス・env・volume)の管理
- 他サービスとの起動順・依存関係
- 起動に失敗したときの検知とログ
- そもそもクラウドのマネージド環境へ移す場合
この記事では「再起動してもコンテナを生かし続ける」手段を、次の3段階で整理する。
- rc.local — 古いやり方
- systemd unit file — 1台サーバーの現代的な正解
- AWS ECS — そもそも自動起動を意識しないマネージド
その前に:compose ファイルはどこに置くべきか
自動起動を仕込む前に、まず compose ファイルの置き場所 を決める。ここがぶれると、後で書く起動スクリプトの参照パスもぶれる。
推奨は /opt/<アプリ名>/ 配下:
/opt/myapp/
├── compose.yaml
├── .env
└── data/
/opt は「そのホストに後から入れたアプリ一式」を置く標準的な場所。root 所有で、システム全体のサービスとして扱いやすい。
❌ アンチパターン:ホームディレクトリに置く
/home/alice/myapp/compose.yaml # 自動起動でハマりやすい
- 自動起動は root 権限で走ることが多く、ユーザーの
$HOME/ PATH / 権限に依存すると起動時に詰まる - そのユーザーが消えたり権限が変わると動かなくなる
- 「誰の環境か」に暗黙依存するのは本番向きではない
👉 開発中は手元で docker compose up するのでホーム配下でも動くが、自動起動に載せる段で必ず刺さる。
| 置き場所 | 用途 | 自動起動との相性 |
|---|---|---|
/opt/<app>/ |
後から入れたアプリ一式(推奨) | ◎ |
/srv/<app>/ |
そのホストが提供するデータ寄りのサービス | ○ |
/home/<user>/ |
開発・お試し | ✗(本番では避ける) |
以降は /opt/myapp/compose.yaml を前提に進める。
段階1:rc.local — 古いやり方
一番素朴なのは /etc/rc.local に起動コマンドを書く方法。起動の最後に一度だけ実行されるスクリプトだ。
# /etc/rc.local
#!/bin/bash
cd /opt/myapp && docker compose up -d
exit 0
実行権限が要る:
chmod +x /etc/rc.local
👉 動くには動く。しかし現代の systemd 環境では /etc/rc.local はデフォルトで無効で、rc-local.service という互換レイヤが拾ってくれるのに頼っている。ディストロによっては自分でこのサービスを有効化しないと動かない。
❌ rc.local の弱点
-
依存関係を書けない:
docker.serviceの起動完了を待たずに叩いて失敗することがある(起動レースの罠) -
失敗検知がない:
docker compose upがコケてもどこにもまともに残らず、気づけない - 再起動ポリシーがない:一度失敗したら終わり。落ちても立て直さない
-
停止処理がない:シャットダウン時に
compose down相当が走らない
「とりあえず動かす」には使えるが、本番のサービスをこれに載せるのは避けたい。次の systemd がほぼ全ての点で上位互換になる。
段階2:systemd unit file — 1台サーバーの正解
1台のサーバーで自動起動させるなら、rc.local ではなく systemd の unit ファイルを書く。rc.local の弱点がすべて埋まる。
# /etc/systemd/system/myapp.service
[Unit]
Description=myapp docker compose
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
Restart=on-failure
[Install]
WantedBy=multi-user.target
有効化して即起動:
systemctl daemon-reload
systemctl enable --now myapp
状態とログの確認:
systemctl status myapp
journalctl -u myapp -f
ポイントを分解する:
-
Requires=docker.service/After=docker.service:Docker が起動してから実行する。rc.local の起動レースが消える -
WorkingDirectory=/opt/myapp:compose の場所をここで一元管理。ExecStart 側にパスを散らさない -
ExecStop=... compose down:シャットダウン時にきれいに落とす -
Restart=on-failure:失敗時に立て直す -
journalctl -u myapp:ログが1か所に集まり、失敗が追える
👉 Type=oneshot + RemainAfterExit=yes は「up -d で起動して抜けるが、サービスとしては起動済み扱いにする」書き方。up -d を使わずフォアグラウンドで張り付かせる書き方もあるが、compose の up/down で組む方が停止処理まで自然に書ける。
rc.local との比較:
| 観点 | rc.local | systemd unit |
|---|---|---|
| Docker との起動順 | 書けない(レース) | After=docker.service |
| 失敗検知・ログ | ほぼなし | journalctl -u |
| 再起動ポリシー | なし | Restart= |
| 停止処理 | なし | ExecStop= |
| 有効/無効の切替 | 手作業 | systemctl enable/disable |
👉 compose ファイルを /opt/myapp に固定し、WorkingDirectory で指すだけ。場所とサービス定義が素直に対応する。
段階3:AWS ECS — 自動起動を意識しない世界
規模が上がると発想が変わる。「ホストの再起動時に何をするか」を自分で書くのをやめる。
AWS ECS では、タスク定義(何をどう動かすか)と、サービスの desired count(何個動かし続けるか)をプラットフォームが維持する。
- systemd の
Restart=に相当するのが ECS サービススケジューラ。desired count を常に保とうとする - コンテナが落ちても、ホスト(EC2 / Fargate)が飛んでも、別の場所で立て直される
- 「サーバーを再起動する」「起動時に何を実行する」という運用概念そのものが薄くなる
compose からの移行イメージ:
| docker compose | ECS |
|---|---|
services: の1サービス |
タスク定義のコンテナ定義 |
restart: always |
サービスの desired count |
depends_on / 起動順 |
タスク内依存・サービス設計 |
.env / environment:
|
タスク定義の environment / Secrets |
| ホストの systemd unit | (不要。スケジューラが担う) |
❌ 誤解:「ECS なら全部楽になる」わけではない
- VPC・サブネット・セキュリティグループなどネットワーク設計が要る
- タスク実行ロール / タスクロールなど IAM の設計が要る
- ログは CloudWatch 側の設定になる
👉 補足:昔あった docker compose の ECS 連携(Docker Compose CLI + ECS context)は非推奨。今は素直にタスク定義(や IaC)を書くのが基本。
つまり ECS は「起動を生かし続ける」という一点では自分で unit を書く必要がなくなる代わりに、別の設計コストを払う世界だ。
使い分けの早見表
| 観点 | rc.local | systemd unit | AWS ECS |
|---|---|---|---|
| 位置づけ | 古い / 非推奨 | 1台サーバーの正解 | マネージドの終着点 |
| 依存管理 | 書けない |
After / Requires
|
サービス設計で吸収 |
| 失敗検知・ログ | ほぼなし | journalctl |
CloudWatch |
| 再起動ポリシー | なし | Restart= |
desired count |
| ホスト障害からの復帰 | 不可 | 同一ホスト再起動のみ | 別ホストで再スケジュール |
| 運用コスト | 低(だが脆い) | 中 | 高(設計が要る)/ 運用は楽 |
まとめ
-
docker run --restart/restart: alwaysは Docker デーモンの再起動には効くが、ホスト再起動や compose 一式の管理には不十分 - compose ファイルは
/opt/<app>/に置き、ホーム配置は避ける。自動起動で刺さる - 1台サーバーなら rc.local ではなく systemd unit file を書く。依存(
After)・ログ(journalctl)・再起動ポリシー(Restart=)・停止処理(ExecStop)が揃う - 規模が上がったら「自動起動を自分で書く」発想を捨てて ECS などマネージドへ。desired count がスケジューラに任せる形になる
👉 手段は「素朴だが脆い」→「1台で堅い」→「そもそも意識しない」と登っていく。今どの段にいるかで選べばよい。