外部リポジトリ(アプリ本体)を丸ごと取り込み、複数コンテナ(app / nginx / db 等)で共有して動かすタイプのプロジェクト向けのガイドです。pre-up ライフサイクルフックで ホスト側リポジトリの clone/pull と 共有 work ボリュームへの populate を行い、2 回目以降の devbase up では populate 済みを検出して同期をスキップする冪等パターンを解説します。
リファレンス実装は Laravel Sail ベースの carmo-system-console プラグインです。このリポジトリには含まれません — 社内向けの private プラグインレジストリで配布されており、devbase plugin install 後に projects/carmo-system-console/(projects/ は .gitignore 対象)へ展開されます。アクセス権が無い場合でも、本書のコード断片と チェックリスト だけでパターンを再現できます。
前提: ライフサイクルフック自体の基本は プラグイン開発クイックスタート を、共有ボリュームや
scaleの一般論は compose.yml ガイドライン と コンテナ操作ガイド を参照してください。本書はそれらを組み合わせた「repo 連携」パターンに絞って説明します。
devbase-general / devbase-php のような単一 dev コンテナのプロジェクトでは、各コンテナが専用の /work ボリュームを持ち、ソースはコンテナ内で git clone すれば十分です。
一方で、アプリ本体のリポジトリに付属する docker-compose.dev.yml 相当(app / nginx / mysql / redis …)を devbase 上で再現したい場合、次の要件が生じます。
- 複数コンテナが同一のソースツリーを共有する必要がある(app が書いた成果物を nginx が配信する等)。
- app サービスは リポジトリ内の
Dockerfileをビルドコンテキストとして使うため、ホスト側にソースの実体が必要。 - コンテナ内
git cloneに頼ると、複数コンテナの起動順で clone レースが起きる。
これを解決するのが「ホスト repo/ を用意し、それを共有 work ボリュームへ populate してから全コンテナを起動する」パターンです。populate を pre-up(docker compose up の前)に寄せることで、app / nginx / mysql が立ち上がる前にソースを確定できます。
graph TD
S["リモート git リポジトリ<br/>(volareinc/app 等)"] -->|"pre-up ① clone/pull"| R["ホスト ./repo<br/>(app のビルドコンテキスト)"]
S3["S3<br/>env/<env>.env"] -->|"pre-up ② 取得"| E["ホスト ./.env<br/>(compose 変数展開用)"]
R -->|"pre-up ③ populate"| V["共有 work ボリューム<br/>/work/<リポジトリ名>"]
E -->|"pre-up ④ 配置"| V
V --> A["app コンテナ /work"]
V --> N["nginx コンテナ /work:ro"]
V --> M["mysql コンテナ /work:ro"]
V --> D["dev コンテナ /work"]
| 要素 | 実体 | 役割 |
|---|---|---|
ホスト ./repo |
git clone した作業コピー |
app イメージのビルドコンテキスト兼、work ボリュームの populate 元 |
ホスト ./.env |
S3 から取得 | docker compose の変数展開(${DB_DATABASE} 等)に使用 |
| 共有 work ボリューム | external: true の named volume |
全コンテナが /work にマウントする実行時ソース |
compose.yml では work ボリュームを external として宣言し、インスタンスごとに名前を切り替えます。
services:
app:
build:
context: ./repo # ← ホスト repo/ をビルドコンテキストに
dockerfile: docker/Dockerfile
volumes:
- work:/work # ← 共有 work ボリューム
nginx:
volumes:
- work:/work:ro
# ...
volumes:
work:
external: true
name: ${DEVBASE_WORK_VOLUME:-devbase_work_${DEVBASE_INSTANCE_INDEX:-1}}Note:
pre-upは子プロセスのためexport DEVBASE_WORK_VOLUMEしても後続のdocker compose upへは伝播しません。compose.yml側は${DEVBASE_WORK_VOLUME:-devbase_work_${DEVBASE_INSTANCE_INDEX:-1}}のフォールバック式で解決し、加えてpre-upが同じ値を.envに書き出すことで整合を取ります。
このパターンは scale=1(1 プロジェクト = 1 work ボリューム)を前提としています。 devbase の既定は 2 なので、プロジェクトの project.yml に scale: 1 を明示してください。
現行実装では、scale>1 にすると「全コンテナが同一のソースツリーを共有する」という本パターンの前提が次の 2 点で崩れます。
pre-upはインデックスなしで 1 回しか実行されない。 devbase がDEVBASE_INSTANCE_INDEXを環境変数として渡すのは、インスタンスごとに実行されるdeployフックだけです。pre-upには渡らないため、populate されるのは.env(または既定値1)で解決される 単一の work ボリュームのみで、devbase_work_2以降は空のまま残ります。- scale 生成が書き換えるのは dev サービスの
/workだけ。.docker-compose.scale.ymlの生成ではdev-<index>の/workマウントがdevbase_work_<index>へ固定で差し替えられる一方、app / nginx / mysql などの非 dev サービスはcompose.ymlに書いた共有 work ボリュームを参照し続けます。結果として dev-2 以降だけが別(空の)ボリュームを見ることになります。
さらに、同一リポジトリを複数インスタンスへ分離して並行稼働させることは現行実装では未サポートです。回避策として思いつく 2 つの手はいずれも成立しません。
DEVBASE_WORK_VOLUMEで名前を分ける。 scale 生成は dev サービスの/workをdevbase_work_<index>(scale=1 なら常にdevbase_work_1)へ無条件に差し替えます(compose.ymlに/workマウントを書いていなくても追加されます)。DEVBASE_WORK_VOLUMEが効くのは app / nginx など非 dev サービスだけなので、既定名以外を指定すると dev だけが別ボリュームを見る分裂状態になります。- プロジェクトディレクトリごと複製する。 work ボリュームは
COMPOSE_PROJECT_NAMEの接頭辞が付かないグローバルな external ボリュームなので、複製先も同じdevbase_work_1を共有します。分離になりません。
同一リポジトリを同時に複数環境で動かす必要がある場合は、Docker ホスト(docker context)そのものを分けてください。なお 別リポジトリの repo 連携プロジェクト同士は、populate 先が /work/<リポジトリ名> とサブディレクトリで分かれるため、同じ work ボリュームを共有したまま共存できます。
pre-up は毎回の devbase up 前に次を行います。
| # | 処理 | 内容 |
|---|---|---|
| ① | repo/ の clone / pull |
無ければ git clone "$DEVBASE_PRIMARY_URL"、あれば git pull --ff-only(app ビルドコンテキストの最新化) |
| ② | .env の取得 |
S3 等から取得してホスト ./.env に配置(docker compose の変数展開前に必要) |
| ③ | work ボリュームへ populate | repo/ の内容を /work/$DEVBASE_PRIMARY_DIR へコピー |
| ④ | .env を work ボリュームへ配置 |
Laravel 等のランタイムが /work/$DEVBASE_PRIMARY_DIR/.env を参照するため |
pre-up はホスト側で動くフックなので、コンテナへ渡る env は読み込まれません。populate 先のディレクトリ名と clone URL は、devbase が project.yml から解決して環境変数で渡します。
#!/bin/bash
# projects/<name>/pre-up
set -e
REPO_DIR="$DEVBASE_PRIMARY_DIR" # 例: carmo-system-console
WORK_VOLUME="${DEVBASE_WORK_VOLUME:-devbase_work_1}"
# ① ビルドコンテキストの clone / pull
if [ ! -d "./repo/.git" ]; then
git clone "$DEVBASE_PRIMARY_URL" repo
elif [ "${DEVBASE_REPO_PULL:-1}" = "1" ]; then
git -C repo pull --ff-only
fi
# populate 済み判定は work ボリューム上の /work/$REPO_DIR/.git で行う
if docker run --rm -v "$WORK_VOLUME:/work" alpine test -d "/work/$REPO_DIR/.git"; then
echo "populate 済みのためスキップ"
exit 0
fi
# ②③④ (.env 取得 / populate / .env 配置) は /work/$REPO_DIR を対象に行う変数の一覧と deploy への渡り方は クイックスタート「フックへ渡る環境変数」 を参照してください。project.yml に複数リポジトリを書いている場合は、DEVBASE_REPO_DIRS(宣言順・空白区切り)で全 clone 先を回せます。
② を deploy(up 後フック)ではなく pre-up で行うのは、compose.yml の MYSQL_DATABASE: ${DB_DATABASE:-...} のような変数展開が docker compose パース時(= MySQL コンテナ初回起動前)に .env を要求するためです。deploy 段階では間に合わず、DB がデフォルト名で初期化されてしまいます。
このパターンの肝は「初回だけ populate し、2 回目以降はコンテナ側に触れない」ことです。
pre-up は work ボリューム上に /work/<リポジトリ名>/.git が存在するかどうかで populate 済みを判定し、済みの場合は ②③④ をスキップします。
| # | 処理 | 未populate(初回) | populate 済み(2回目以降) |
|---|---|---|---|
| ① | repo/ の git pull |
実行 | 実行(構成変更をビルドに追従) |
| ② | .env の S3 取得 |
実行 | スキップ |
| ③ | ソース populate | 実行 | スキップ |
| ④ | .env を volume へ配置 |
実行 | スキップ |
populate 済みの work ボリュームを毎回ホスト repo/ で上書き同期すると、次の破壊が起きます。
- 同期の除外リスト(
storage//vendor//node_modules//.env等)に無いファイルが消える。 コンテナ内で生成した認証ファイルや作業ファイルがdevbase upのたびに削除される。 - コンテナ側で編集した
.envが上書きされる。
これを避けるため、実行時ソースと .env の供給は初回 populate 時に限定し、以降はコンテナ側を手動管理に委ねます。これはアプリ本体リポジトリが取る一般的な開発フローと同じ考え方です。多くのリポジトリでは、環境ファイルの取得やソースの用意はビルド時のセットアップスクリプトが担い、日常の起動(docker compose up)は環境ファイルやソースに触れません。devbase の初回 populate がこのビルド時セットアップに相当し、2 回目以降の up は起動だけを行います。
一方で ①(repo/ の pull)は常に実行します。これはホスト側のビルドコンテキストであり、compose.yml / Dockerfile / docker/ 構成の変更を次回の app イメージ再ビルドへ反映するためです(アプリのソースコードそのものは work ボリューム側で管理)。
populate 済み以降、更新経路は次のように分かれます。
| 対象 | 場所 | 更新方法 |
|---|---|---|
| ビルドコンテキスト | ホスト ./repo |
pre-up が毎回 git pull(自動) |
| 実行時ソース | work ボリューム /work/<リポジトリ名> |
コンテナ内で手動 git pull |
実行時 .env |
work ボリューム /work/<リポジトリ名>/.env |
コンテナ内で手動編集 |
# 実行時ソースの更新(dev コンテナ内)
cd /work/<リポジトリ名>
git pull origin main.env やソースを S3 / repo/ の内容からやり直したい場合は、populate 済み判定に使われる /work/<リポジトリ名> を消して、次回 up で populate を再実行させます。
Warning: work ボリューム(既定
devbase_work_1)はCOMPOSE_PROJECT_NAMEの接頭辞が付かない グローバルな external ボリュームで、同じインスタンス index を使う すべての devbase プロジェクトが共有します。docker volume rmでボリュームごと消すと、停止中の別プロジェクトのソースや生成物まで巻き添えで失われます。プロジェクトの分離単位はボリュームではなく/work/<リポジトリ名>サブディレクトリなので、通常はサブディレクトリだけを削除してください。
推奨: このプロジェクトのサブディレクトリだけを削除する
devbase down
# 何が入っているか(=他プロジェクトが同居していないか)を確認
docker run --rm -v devbase_work_1:/work alpine ls -la /work
# このプロジェクトのソースだけを削除(<リポジトリ名> は project.yml の repos[].dir)
docker run --rm -v devbase_work_1:/work alpine rm -rf /work/<リポジトリ名>
devbase up # pre-up が ②③④ を再実行ボリュームごと作り直す場合(他プロジェクトが同じ work ボリュームを使っていないことを確認してから)
devbase down
# このボリュームをマウントしているコンテナを列挙(停止中も含む)
docker ps -a --filter volume=devbase_work_1 --format '{{.Names}}'
docker volume rm devbase_work_1 # external volume のため project 名の接頭辞は付かない
# DEVBASE_WORK_VOLUME を設定している場合はその名前
devbase upNote: どちらの手順でも、削除前にコンテナ内で加えた変更(未コミットのソース変更、編集した
.env)をコミット / 退避してください。DB 等のsail-*ボリュームは別管理なので、work ボリュームを消してもデータは残ります。
| 変数 | 既定 | 効果 |
|---|---|---|
DEVBASE_REPO_PULL |
1 |
0 にすると ①(repo/ の git pull)を抑止。オフラインや意図的にビルドコンテキストを固定したいとき |
DEVBASE_ENV_OVERWRITE |
backup |
未 populate 時の既存ホスト .env の扱い。backup(.env.bak.<ts> に退避して上書き)/ skip(既存があれば S3 取得しない)/ force(退避せず上書き) |
DEVBASE_WORK_VOLUME |
devbase_work_<index> |
compose.yml が参照する共有 work ボリューム名の明示指定。未指定なら DEVBASE_INSTANCE_INDEX から解決。ただし効くのは app / nginx など非 dev サービスだけで、dev サービスの /work は scale 生成時に devbase_work_<index> へ無条件に差し替えられます。dev から実行時ソースを触る本パターンでは 既定名のままにしてください(スケール前提 を参照) |
DEVBASE_INSTANCE_INDEX |
1 |
work ボリューム名のインデックス。devbase 本体が渡すのは deploy フックに対してのみで、pre-up や docker compose のプロセス環境には渡りません。compose.yml の ${DEVBASE_INSTANCE_INDEX:-1} は .env に書かれた値、無ければ 1 に解決されます(スケール前提 を参照) |
上記はプロジェクト側が設定する変数です。これに対し、次の 4 つは devbase が project.yml から解決して pre-up / deploy の両方へ渡す読み取り専用の値です(詳細は クイックスタート「フックへ渡る環境変数」)。
| 変数 | 内容 |
|---|---|
DEVBASE_PRIMARY_DIR |
primary リポジトリの /work 配下ディレクトリ名(populate 先) |
DEVBASE_PRIMARY_URL |
primary リポジトリの clone URL |
DEVBASE_WORK_DIR |
コンテナ内の既定の作業ディレクトリ |
DEVBASE_REPO_DIRS |
全リポジトリのディレクトリ名(宣言順・空白区切り) |
Note:
.envの環境選択(例:s3://.../env/local.envのlocal部分)など、S3 パスやプロファイルはプロジェクト固有の変数(例:CARMO_ENV)で制御することがあります。プロジェクトのpre-up冒頭コメントを参照してください。
-
project.ymlにrepos(owner/repo)を定義した -
project.ymlにscale: 1を明記した(既定は2。スケール前提 を参照) -
compose.ymlで work ボリュームをexternal: true+name: ${DEVBASE_WORK_VOLUME:-devbase_work_${DEVBASE_INSTANCE_INDEX:-1}}で宣言した - app サービスの
build.contextをホスト./repoにした -
pre-upで ①clone/pull → ②.env取得 → ③populate → ④.env配置 を実装した -
pre-upが clone 先・URL をDEVBASE_PRIMARY_DIR/DEVBASE_PRIMARY_URLから受け取っている(source ./envでも値を直書きでもなく) -
pre-upが/work/<リポジトリ名>/.gitの有無で populate 済みを判定し、②③④ をスキップする - populate 時の owner を
1000:1000(コンテナ内ユーザー)に設定した -
storage//vendor//node_modules/等、初回のみ生成され上書きしたくないパスの扱いを決めた - README にソース・
.envの更新運用(手動 pull / 再 populate)を記載した
- リファレンス実装:
carmo-system-consoleプラグインのpre-up/compose.yml/README.md(社内 private レジストリ配布。インストール後はprojects/carmo-system-console/配下に展開され、このリポジトリには含まれません) - プラグイン開発クイックスタート — ライフサイクルフックの基本
- compose.yml ガイドライン — 共有ボリューム・スケール構成
- コンテナ操作ガイド —
/workボリュームの一般論