はじめに

こんにちは、エンジニアの遠藤です。

これは Docker Desktop for Mac が相対パス指定をこっそり消していることに気付いた話です。

結論

まずは結論から。

Docker Desktop for Mac は、 COPY や ADD 時に、相対パス指定の .. や、 ../ などは無いものとして、ビルドを行っています。

説明

気付いた発端は、あるプロジェクトで、複数の Dockerfile が必要になったことでした。
その際プロジェクトルートに Dockerfile が大量にできてしまい気持ち悪かったので、適当なフォルダを作り、その下に Dockerfile をまとめておきました。

プロジェクト構成としてはこんなイメージになります。

~/sample_pj
 ├─ src                  # プロジェクトのコード諸々
 │  └─ main.py
 ├─ requirements.txt
 └─ docker_files         # 機能ごとに Dockerfileを置きたい
    ├─ api
    │  └─ Dockerfile
    ├─ batch
    │  └─ Dockerfile
    └─ app
       └─ Dockerfile

そもそも、用途ごとにプロジェクト分けたら良くない?
というご意見もありそうですが、共通化して使いたいコードもあり、このような構成を取りました。

移動前の Dockerfile はプロジェクトルートにあったため、こんな内容になっていました。

FROM amazonlinux:latest

# 色々初期セットアップ

CPPY requirements.txt requirements.txt
COPY src src/
RUN pip install -r requirements.txt

よくある内容だと思います。
これを、ディレクトリ移動した後に実行してみます。

$ cd ~/sample_pj/docker/api
$ docker build -t sample_pj_api:latest .
...
failed to compute cache key: "/src" not found: not found

/src なんて、そんなファイルないよ、と怒られてしまいます。
ディレクトリを移動してしまったので、参照できないわけですね。

そこで、以下のように Dockerfile を書き換えました。

FROM amazonlinux:latest

# 色々初期セットアップ

CPPY requirements.txt requirements.txt
# COPY src src/
COPY ../../src src/ # 相対パスにして、上位ディレクトリを指定してみる
RUN pip install -r requirements.txt

これで実行してみます。

$ cd ~/sample_pj/docker/api
$ docker build -t sample_pj_api:latest .
...
failed to compute cache key: "/src" not found: not found

変わりません。
試しに、実行場所をプロジェクトルートにしてみようと思い立ちます。

$ cd ~/sample_pj
$ docker build -t sample_pj_api:latest -f docker/api/Dockerfile .
...
[+] Building xx.xs (x/x) FINISHED

見事、成功しましたね。
なるほど、 docker を実行するディレクトリを変えれば、上位ディレクトリが参照できるのか!
という勘違いをしたまま、プロジェクトは進行していきます。

ようやく、プロジェクトも中盤に差し掛かり、 CI を使ってデプロイできるように準備を進めます。
CI では Mac を使うことは少なく、弊社では bitrise(Ubuntu) を多く使っています。

さて、デプロイのために、 Ubuntu 上でこの Dockerfile を実行してみます。

するとどうでしょう。

COPY failed: forbidden path outside the build context: ../../src

怒られてしまいました。
どの環境でも Dockerfile さえあれば同じ環境が作れるのが魅力のはずなのに、ビルドできないなんて...

というわけで、調べてみると、 Dockerfile の置いてあるディレクトリよりも上位のディレクトリは参照できません、というような内容がヒットしました。
ふむふむ。
確かにプロジェクト外のアレコレを取り込めてしまったら、セキュリティ的によろしくないですもんね。

というわけで、 CI を実行するために、 Dockerfile の内容を変更します。

FROM amazonlinux:latest

# 色々初期セットアップ

CPPY requirements.txt requirements.txt
COPY src src/ # 上位ディレクトリを指定できないので、相対パス指定を消す
# COPY ../../src src/
RUN pip install -r requirements.txt

この頃には、 local で docker build する機会も減っており、誰も困ることなくまたプロジェクトが進みます。

さて、久しぶりに、 Docker にファイルを追加する機会がやってきました。
まずは、 local で動作確認をしようと思いましたが、相対パスは消されているので、「さすがに動かないよなぁ」、と思いながら、色々思い出すためにも、一旦実行してみます。

$ cd ~/sample_pj
$ docker build -t sample_pj_api:latest -f docker/api/Dockerfile .
...
[+] Building xx.xs (x/x) FINISHED

...あれ、成功。

あれあれ、相対パス指定しなくて良いんでしたっけ?

FROM amazonlinux:latest

# 色々初期セットアップ

CPPY requirements.txt requirements.txt
# COPY src src/
COPY ../../src src/ # 相対パスに戻してみる
RUN pip install -r requirements.txt

もう一度、相対パスに戻して実行してみます。

$ cd ~/sample_pj
$ docker build -t sample_pj_api:latest -f docker/api/Dockerfile .
...
[+] Building xx.xs (x/x) FINISHED

成功...はて?

FROM amazonlinux:latest

# 色々初期セットアップ

CPPY requirements.txt requirements.txt
# COPY src src/
COPY ../../../../../../src src/ # 適当に相対パスを追加
RUN pip install -r requirements.txt

意味がわかりません。
こうなったら、やけくそで大量に ../ を追加して実行しちゃいます。

$ cd ~/sample_pj
$ docker build -t sample_pj_api:latest -f docker/api/Dockerfile .
...
[+] Building xx.xs (x/x) FINISHED

案の定、成功しました。

ははーん。

もう、そんなところには src ディレクトリは無いんですよ。
なにより、 ../../../../../../ なんて、 root ディレクトリより上なんです。

なるほど。
こうして、 Docker Desktop for Mac は相対パス指定をこっそり全部消して実行しているくさい、ということに気付きます。
(ビルドしているコードは見れないので、実際のところは分かりませんが...)

そういう観点で調べてみたところ、 1 件、 issues が見つかりました。

https://github.com/docker/for-mac/issues/5794

jrose-signal さんが必死にこれはバグだと訴えかけていますが、無視され続け、 Close されてしまっていました。
これは悲しい。

私も Dockerfile はどの環境でも同じように動くべきだと思っているので、こっそり path の解釈を変更するのはいただけないなと思いました。
同じように、適切にエラーを吐いて欲しいところです。

まとめ

長くなりましたが、最後にもう一度、結論を書いておきます。

  • 上位ディレクトリを参照する場合、 Dockerfile の場所は関係なく、 docker コマンドを実行する場所が重要。
  • Docker Desktop for Mac は、 COPY, ADD 時に相対パスを消してしまうので注意。
  • Mac 版では動いてしまうが、極力、相対パスを書かないようにした方が良い。

バグっぽいので、いつか挙動は変わるかもしれないですね。