自宅サーバーのDocker Compose構成を1ファイルにまとめて管理する(Portainerとの使い分け)

Photo: Ryan Latimer / Pexels
自宅サーバーにコンテナを 3 つ 4 つと足していくと、そのうち「このコンテナ、どのコマンドで立てたんだっけ」が分からなくなります。docker run を手で打った日の履歴はもう残っていません。
先に結論です。サービスが 10 個くらいまでなら、compose.yaml 1 ファイル+.env にまとめて Git に入れるのが一番管理が楽です。 そして Portainer は「見る・止める・ログを読む」ための GUI として使い、デプロイの正本(せいほん)はファイル側に一本化します。 両方から編集できる状態にすると、必ずどちらかが古くなります。
なお docker compose は 2026 年 9 月時点で v5 系(v5.5.1)が最新です。バージョン番号が 2.x から大きく飛んでいますが、コマンド体系は docker compose up -d のままで、書き方を変える必要はありません。
まず 1 ファイルで始める:置き場所を決める
最初に決めるのは中身より置き場所です。私は次の形に落ち着いています。
/srv/homelab/
├── compose.yaml ← 構成の正本。Git 管理
├── .env ← パスワードや個別値。Git 管理しない
├── .env.example ← 変数名だけ書いたテンプレ。これは Git に入れる
└── appdata/ ← 各コンテナの設定・DB(bind mount 先)
ポイントは 3 つです。
appdata/を 1 か所に集める:バックアップ対象が「compose.yamlと.envとappdata/」の 3 つだけになります。Docker の名前付きボリュームは場所が見えにくいので、自宅サーバーでは bind mount のほうが扱いやすいと思います.envは Git に入れない:.gitignoreに書いて、代わりに.env.exampleを置きますname:を明示する:Compose のプロジェクト名は既定でディレクトリ名から決まります。明示しておくと、あとでディレクトリを動かしても同じプロジェクトとして扱われます
ついでに、compose.yaml の冒頭に version: "3.8" と書く古い書き方はもう不要です。現在の Compose では無視され、警告が出ます。消して構いません。
1 ファイルを短く保つ書き方
サービスが増えても 1 ファイルで耐えられるかどうかは、共通部分をどれだけ外に出せるかで決まります。使うのは YAML のアンカーと .env です。
name: homelab
x-common: &common
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
services:
uptime-kuma:
<<: *common
image: louislam/uptime-kuma:1
volumes:
- ${DATA}/uptime-kuma:/app/data
ports:
- "127.0.0.1:3001:3001"
jellyfin:
<<: *common
image: lscr.io/linuxserver/jellyfin:latest
environment:
TZ: ${TZ}
PUID: ${PUID}
PGID: ${PGID}
devices:
- /dev/dri:/dev/dri
volumes:
- ${DATA}/jellyfin:/config
- /srv/media:/media:ro
ports:
- "8096:8096"
vaultwarden:
<<: *common
image: vaultwarden/server:1
environment:
TZ: ${TZ}
volumes:
- ${DATA}/vaultwarden:/data
ports:
- "127.0.0.1:8081:80"
.env 側はこれだけです。
TZ=Asia/Tokyo
PUID=1000
PGID=1000
DATA=/srv/homelab/appdata
x- で始まるキーは Compose が無視する拡張フィールドなので、共通設定の置き場として使えます。&common で名前を付けて、各サービスで <<: *common と展開します。
ここで 1 つだけ注意点があります。この展開はキー単位の上書きで、深いマージはされません。 アンカー側に environment を書き、サービス側でも environment を書くと、サービス側で丸ごと置き換わります。共通の TZ が消えて時刻がずれる、という地味な事故が起きやすいところです。アンカーには restart や logging のような「まるごと共通で困らないもの」だけを入れて、環境変数は各サービスに書くほうが安全です。
ポート公開も見ておきます。外に出す必要がないものは 127.0.0.1:3001:3001 のようにバインド先を絞ります。Docker はポート公開時にファイアウォールを迂回することがあるとされているため、ufw を入れていても LAN 全体から見えてしまうことがあります。
日々の運用は 4 コマンドで足りる
1 ファイルにまとめる一番の効果は、覚えるコマンドが減ることです。
cd /srv/homelab
docker compose config # 構文と変数展開の確認(これを先に)
docker compose up -d # 変更のあったサービスだけ作り直す
docker compose pull && docker compose up -d # イメージ更新
docker compose logs -f --tail=100 jellyfin # 個別のログ
docker compose config は、.env が正しく読まれているかを展開後の内容で見せてくれます。変数が空だと image: :latest のような形で表示されるので、up する前に気づけます。
全体像を見たいときは docker compose ls です。ホスト上で動いている Compose プロジェクトと、その compose.yaml のパスが一覧で出ます。Portainer を入れる前に、まずこれで足りることが多いです。
更新のときに 1 つだけ。latest タグを全サービスに使うのは避けたほうがいいと思います。 pull した瞬間にメジャーバージョンが上がって、設定ファイルの互換性が切れることがあります。uptime-kuma:1、vaultwarden/server:1 のようにメジャー固定にしておくと、pull が安全な操作になります。
ちなみに Compose v5 系では、コンテナを作り直す条件の判定(既存の状態と定義の突き合わせ)が見直され、以前は無駄に再作成されていたケースが減っています。up -d を打つ心理的な抵抗は前より小さくなりました。
10 個を超えたら include で分ける
1 ファイルの限界は、体感では 200 行、サービス 10 個あたりです。そこを超えたら include で分割します。ファイルを分けても同じ 1 つのプロジェクトとして扱われるのが、単に複数ディレクトリで up するのとの違いです。
name: homelab
include:
- path: ./media/compose.yaml
- path: ./monitoring/compose.yaml
services:
# ルートに残すのは共通のものだけ
もう 1 つ便利なのが profiles です。普段は起動しないもの(速度測定、たまにしか使わないツール)を同じファイルに置いたまま、既定では起動しない状態にできます。
speedtest:
image: openspeedtest/latest
profiles: ["tools"]
docker compose --profile tools up -d で必要なときだけ起動します。「消すほどではないが常時は要らない」ものを、コメントアウトで残さずに済むのが効きます。
バックアップは、この compose.yaml と .env、appdata/ を外部へコピーするだけです。書き込み中の DB を含むので、止められるサービスは docker compose stop してから取るのが安全です。外付けSSDを 1 本挿しておいて、週 1 回 rsync で同期する程度でも、何も無い状態とは大違いです。
Portainer はどこまで使うか
ここが本題です。Portainer CE(無料版。2026 年 9 月時点で 2.45 系)は Docker の GUI として非常に出来がよく、入れて損はありません。ただし Stacks 機能で構成を書き始めると、正本が 2 か所になります。
Portainer の Stacks は、Web UI に貼り付けた Compose の内容を Portainer 自身のデータベースに保存します。一方、CLI で docker compose up -d したプロジェクトを Portainer から見ると「Portainer の外で作られたスタック」として表示され、操作が限定されます。Portainer 側に元の定義や環境変数、履歴が無いためで、これは仕様とされています。
つまり、どちらで作るかを最初に決める必要があります。私のおすすめは次の線引きです。
| やること | どちらで |
|---|---|
| 構成の編集・サービス追加・更新 | ファイル(CLI)。Git に履歴が残る |
| ログを読む・コンテナに入る | Portainer。docker exec を打たずに済む |
| 状態確認(CPU・メモリ・再起動回数) | Portainer。一覧性が高い |
| 家族に「再起動だけ」させたい | Portainer。権限を絞ったユーザーを作れる |
| バックアップ対象の把握 | ファイル。compose.yaml を見れば分かる |
両取りしたい場合は、Portainer の Git 連携(GitOps) を使います。Stacks を Git リポジトリに紐づけると、リポジトリを正本にしたまま Portainer から再デプロイでき、更新の検知と自動反映も設定できます。自宅サーバー 1 台なら CLI で十分ですが、台数が増えたときの選択肢として覚えておくとよいと思います。
セキュリティ面も 1 行だけ。Portainer は /var/run/docker.sock をマウントして動きます。これはホストの root 相当の権限です。インターネットに直接公開せず、LAN か Tailscale 経由でのみ触れる場所に置いてください。
やらないほうがいいこと
- Portainer の UI と
compose.yamlを両方から編集する:次にup -dした人が、相手の変更を消します appdataをコンテナごとにバラバラの場所へ置く:バックアップ対象が数えられなくなります.envを Git に入れる:git rm --cachedしても履歴には残ります。最初から入れないのが唯一の対策です- 全部
latest:更新が「運任せ」になります。少なくとも DB を持つサービスはメジャー固定に docker compose down -v:-vはボリュームごと消します。downとstopを打ち間違えるより痛いです
まとめ
- 自宅サーバーのコンテナは
compose.yaml1 ファイル+.envにまとめ、appdata/と一緒に Git とバックアップの対象を 3 つに絞るのが管理しやすいです - 共通設定は
x-commonアンカーへ。ただし<<:は深いマージをしないので、environmentはサービス側に書くほうが安全です - 日常は
config→up -d→pull && up -d→logsの 4 つで足ります。docker compose lsで全体も見えます - サービス 10 個・200 行を超えたら
include。たまにしか使わないものはprofilesに逃がします - Portainer は「見る側」に徹するのが安全です。Stacks で書くと正本が Portainer の DB へ移り、CLI で作ったスタックは操作が限定されます
- 両方使いたいなら Git 連携(GitOps)。そして Portainer は
docker.sockを握るので外部公開しないこと
構成をファイルに書き出しておく本当の価値は、サーバーを入れ替えるときに出ます。新しいミニPC に Docker を入れて、compose.yaml と .env と appdata/ を持っていって up -d するだけで戻る——その状態を作れているかどうかが、自宅サーバーを長く続けられるかの分かれ目だと思います。
運営者が作った Excel テンプレートを BOOTH で配布しています。IT 資産管理台帳(無料 Lite 版あり) / IT 資格の学習管理シート


