概要
WSL上のRancher DesktopでDifyをDocker Composeから起動したところ、nginxとsandboxが再起動ループに入り、Difyへアクセスできなくなった。
調査の結果、ホストに存在する設定ファイルがCompose経由ではコンテナへ正しくマウントされず、ファイルがディレクトリになったり、マウント先が空になったりしていた。
Rancher Desktop(Docker Desktopもかも)の場合、docker run -vではうまくいくが、compose経由のマウントがうまくいかないことがあるらしい。
最終的に、設定ファイルを派生DockerイメージへCOPYしてマウントではなくイメージに焼き込み、専用entrypointから読み込むことでbind mountへの依存を回避した。その時に備忘録。
環境
- WSL2 Ubuntu
- Rancher Desktop 1.23.1
- Docker Compose
発生した問題
Difyを次のコマンドで起動した。
docker compose -f docker/docker-compose.yaml up -d
nginxコンテナがエラーを吐いていたため、down後、-d オプションを外して、コンテナログを確認すると、nginxとsandboxが再起動を繰り返すエラーを確認。
nginxで発生していたエラー
nginxのログには次のエラーが出ていた。
cp: -r not specified; omitting directory '/docker-entrypoint-mount.sh'
nginx exited with code 1
Composeでは、ホスト側のentrypointをコンテナへbind mountしていた。
本来、/docker-entrypoint-mount.shはファイルとしてマウントされるはずである。ところが、コンテナ内ではディレクトリとして認識されていた。
必要ファイルを誤って消してしまったかもしれないので、再cloneと、volumeの削除、コンテナの再生成を行ったが同じエラーが発生する。
最小構成でのbind mountテスト
Dify固有の問題か、Docker全体の問題かを切り分けるため、docker runで最小構成のマウントを試した。
ファイルのマウント
docker run --rm \
-v /home/dify/docker/nginx/docker-entrypoint.sh:/x \
alpine:3.20 \
ls -la /x
ディレクトリのマウント
docker run --rm \
-v /home/dify/docker/nginx/conf.d:/conf \
alpine:3.20 \
ls -la /conf
このテストでは、ファイルとディレクトリの両方が正常にマウントされた。
この結果から、次のことが分かった。
- WSL上のbind mountが全面的に壊れているわけではない
- ホスト側ファイルも正常に存在する
- Difyのファイル構成にも問題はない
- Rancher DesktopとDocker Composeを組み合わせた場合に再現する
Rancher Desktop側のマウント状態
docker inspectでマウント情報を確認すると、ホストパスが直接使われているのではなく、Rancher Desktop
が生成した中間パスが使用されていた。
/mnt/wsl/rancher-desktop/run/docker-mounts/...
概念的には次の経路になる。
WSL上のホストファイル
↓
Rancher Desktopの中間マウント
↓
Dockerコンテナ
Compose上のパスは正しいものの、この中間マウントを通過した後、コンテナ内ではファイルがディレクトリとして見えていた。
このため、Rancher DesktopとCompose間のパス変換またはマウント生成処理で問題が起きている可能性が高いと判断した.ただし、Rancher Desktop内部のどの処理が原因なのかまでは特定していない。
原因の整理
確認できた事実は次のとおり。
- ホスト側のファイルは存在する
- Composeが解決したパスも正しい
- コンテナを再作成しても再現する
- WSLとRancher Desktopを更新しても再現する
- 単純なdocker run -vでは正常に動く
- Compose経由ではファイルがディレクトリになる
- Compose経由ではディレクトリの内容が空になる
- nginxとsandboxの両方で発生する
以上から、原因はDifyの設定ファイル不足ではなく、WSL、Rancher Desktop、Docker Compose間のbind mount処理にある可能性が高い。
回避方針
bind mountが安定しないため、設定ファイルを派生DockerイメージへCOPYする方針にした。
ただし、設定ファイルを元のマウント先へ直接COPYするだけでは不十分である。
例えばsandboxの/confへ直接コピーしても、コンテナ起動時にbind mountされると、イメージ内の/confがマウントによって隠される。そこで次の構成にした。
Dockerイメージ
/opt/dify-sandbox/conf/config.yaml
↓ 起動時にコピー
bind mountされた /conf/config.yaml
↓
/mainを起動
イメージ内の別パスへ設定を保存し、独自entrypointから実際の利用先へコピーする。
nginxの回避実装
FROM docker.io/library/nginx:1.31.1
COPY nginx/nginx.conf.template /opt/dify-nginx/nginx.conf.template
COPY nginx/proxy.conf.template /opt/dify-nginx/proxy.conf.template
COPY nginx/https.conf.template /opt/dify-nginx/https.conf.template
COPY nginx/conf.d/default.conf.template /opt/dify-nginx/conf.d/default.conf.template
COPY nginx/docker-entrypoint-embedded.sh /docker-entrypoint-embedded.sh
RUN chmod +x /docker-entrypoint-embedded.sh
テンプレートをbind mount先ではない/opt/dify-nginxへ保存する。
#!/bin/sh
set -eu
TEMPLATE_DIR="${NGINX_TEMPLATE_DIR:-/opt/dify-nginx}"
CONF_DIR="/etc/nginx/conf.d"
mkdir -p "$CONF_DIR"
env_vars=$(printenv | cut -d= -f1 | sed 's/^/$/g' | paste -sd, -)
envsubst "$env_vars" \
< "$TEMPLATE_DIR/nginx.conf.template" \
> /etc/nginx/nginx.conf
envsubst "$env_vars" \
< "$TEMPLATE_DIR/proxy.conf.template" \
> /etc/nginx/proxy.conf
envsubst "$env_vars" \
< "$TEMPLATE_DIR/conf.d/default.conf.template" \
> "$CONF_DIR/default.conf"
exec nginx -g 'daemon off;'
このentrypointは、問題のある/docker-entrypoint-mount.shを参照しない。イメージ内のテンプレートからnginx設定を生成し、そのままnginxを起動する。
通常のComposeファイルを直接変更せず、WSL用のoverrideを追加した。
services:
nginx:
image: dify-nginx-wsl:local
build:
context: .
dockerfile: nginx/Dockerfile.wsl
entrypoint:
- /docker-entrypoint-embedded.sh
sandbox:
image: dify-sandbox-wsl:local
build:
context: .
dockerfile: sandbox/Dockerfile.wsl
entrypoint:
- /entrypoint-embedded.sh
起動時は通常構成とWSL用overrideを重ねる。
docker compose \
-f docker/docker-compose.yaml \
-f docker/docker-compose.wsl.yaml \
up -d --build
後ろに指定したdocker-compose.wsl.yamlが、nginxとsandboxのイメージ、ビルド設定、entrypointだけを上書きする。
注意点
この回避策では、設定ファイルをDockerイメージへ組み込んでいる。
そのため、次の場合はイメージの再ビルドが必要になる。
- nginxの設定テンプレートを変更した場合
- sandboxのconfig.yamlを変更した場合
- ベースイメージを更新した場合
docker compose \
-f docker/docker-compose.yaml \
-f docker/docker-compose.wsl.yaml \
up -d --build
また、パスワードやAPIキーなどの秘密情報をイメージへ直接COPYするのは避けるべきである。秘密情報は環境変数やDocker secretsなどで注入する方がよい。
一度イメージに秘密情報を焼き込んでしまった場合、リポイ時取りにプッシュしてしまうと、後から気づいた時に結構面倒なことになるので注意。
今回イメージへ組み込んだのは、Difyの起動に必要な静的設定ファイルである。
GitHubのissuesをGPTに調査させたところ、似たような事例が結構前からopenのまま残っている。
Rancher DesktopやDocker DesktopとWSL内Ubuntuでの開発では、特にcompose周りの挙動が怪しいことがあり、はまりポイントが多い気がする。(ネットワークとか、マウントとか)
wsl containersに期待。