🎁

docker compose buildの --ssh option(w/ BuildKit)を試す

2022/05/03に公開

docker compose v2.4.0 から対応している --ssh option について、Python パッケージの install を例にまとめています。

環境

  • Mac OS Monterey version 12.0.1
  • Docker for Mac 4.7.1 (Engine: 20.10.14)
❯ docker version
Client:
 Cloud integration: v1.0.23
 Version:           20.10.14
 API version:       1.41
 Go version:        go1.16.15
 Git commit:        a224086
 Built:             Thu Mar 24 01:49:20 2022
 OS/Arch:           darwin/arm64
 Context:           default
 Experimental:      true

Server: Docker Desktop 4.7.1 (77678)
 Engine:
  Version:          20.10.14
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.15
  Git commit:       87a90dc
  Built:            Thu Mar 24 01:45:44 2022
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.5.11
  GitCommit:        3df54a852345ae127d1fa3092b95168e4a88e2f8
 runc:
  Version:          1.0.3
  GitCommit:        v1.0.3-0-gf46b6ba
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

やりたいこと

GitHub の private repository から独自パッケージを pip install して、docker image の build を行うようなケースを想定しています。

単純な docker build であれば、--ssh option は対応している一方で、ローカル開発環境では docker-compose (docker compose) を頻繁に利用します。
v2.4.0 以前の docker compose build コマンドは --ssh option は対応していない状況でした。

https://github.com/docker/compose/issues/7025

BuildKitについて

BuildKit に関する詳細な説明は割愛しますが、本記事で記載する内容は BuildKit を利用した image build が前提となります。
Docker 18.09 から、DOCKER_BUILDKIT=1で環境変数を指定することで、BuildKit が利用できるようになっています。

BuildKit に関しては、下記の資料・ドキュメントが参考になるかと思います。

docker

ssh keyの利用 (w/ BuildKit)

上記でも記載した通り、Docker 18.09 から BuildKit が利用できるようになっています。
docker build であれば、--ssh option は対応しており、下記のドキュメントを参考に Dockerfile を記述できます。

https://docs.docker.com/develop/develop-images/build_enhancements/#using-ssh-to-access-private-data-in-builds

例えば、Flask App を起動する開発用の docker image を build したい場合を考えます。
requirements.txtには、install したい python パッケージを管理している GitHub repository が指定されています。

requirements.txt
...
sloppy @ git+ssh://git@github.com/taxintt/sloppy.git@0.1.0 ; python_version >= "3.9" and python_version < "4.0"
uvicorn==0.17.6; python_version >= "3.7"

上記を踏まえて、Dockerfile は下記のようになります。
--mount=type=sshで指定することで、RUNで定義されたコマンドでクライアント側の ssh-agent を利用するようになります。
(注意点として、ssh-addで ssh-agent に対して、事前に GitHub で利用している秘密鍵を登録する必要があります。)

# syntax=docker/dockerfile:1
ARG PYTHON_VER=3.9

FROM python:${PYTHON_VER}-buster as app-base

COPY requirements.txt .

RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
RUN --mount=type=ssh pip install --no-cache-dir -r requirements.txt

RUN flask run --host=0.0.0.0 --port=5000 --debugger --reload

準備ができたら、Dockerfile があるディレクトリで下記のコマンドを実行して image build を行うことができます。

$ export DOCKER_BUILDKIT=1 
$ docker image build -t pip-install-private-repo-sample .

docker compose

--ssh option以前のworkaround

記載している通り、v2.4.0 以前の docker compose では --ssh option は対応していませんでした。
回避策として、--target option でパッケージインストールを行うステージを指定して、事前に docker build を行うことで回避できました。

# 1. pkg (パッケージインストールを行うステージ) をtargetとして指定してimage buildを行う
$ export DOCKER_BUILDKIT=1 
$ docker image build --ssh default --target=pkg .

# 2. docker compose buildを通す
$ export COMPOSE_DOCKER_CLI_BUILD=1 
$ export DOCKER_BUILDKIT=1 
$ docker compose build

--ssh option (^v2.4.0)

上記のような回避方法もありましたが、docker compose v2.4.0 のリリースで、docker compose build で --ssh option が対応するようになりました。

https://github.com/docker/compose/releases/tag/v2.4.0

Mac 用の Docker Desktop であれば、4.7.0 で docker compose のバージョンが v2.4.1 になっています。

https://docs.docker.com/desktop/mac/release-notes/#docker-desktop-470

Docker for Mac 4.3.2 (Engine: 20.10.11)の状態では、docker compose buildを実行すると下記のように pip install が失敗します。

=> [libs 3/4] RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts                                                                                                                  6.6s
 => ERROR [libs 4/4] RUN --mount=type=ssh pip install --no-cache-dir -r requirements.txt                                                                                                                    2.1s
------                                                                                                                                                                                                           
 > [libs 4/4] RUN --mount=type=ssh pip install --no-cache-dir -r requirements.txt:                                                                                                                               
#12 1.000 Ignoring colorama: markers 'python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" or platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.5.0"' don't match your environment
#12 1.002 Collecting sloppy@ git+ssh://git@github.com/taxintt/sloppy.git@0.1.0
#12 1.002   Cloning ssh://****@github.com/taxintt/sloppy.git (to revision 0.1.0) to /tmp/pip-install-gd771hcn/sloppy_7e4edbf65fe949d795d03e7ff53cb5a3
#12 1.014   Running command git clone --filter=blob:none --quiet 'ssh://****@github.com/taxintt/sloppy.git' /tmp/pip-install-gd771hcn/sloppy_7e4edbf65fe949d795d03e7ff53cb5a3
#12 1.581   Warning: Permanently added the ECDSA host key for IP address '13.114.40.48' to the list of known hosts.
#12 1.951   git@github.com: Permission denied (publickey).
#12 1.955   fatal: Could not read from remote repository.
#12 1.955 
#12 1.955   Please make sure you have the correct access rights
#12 1.970   and the repository exists.
#12 1.975   error: subprocess-exited-with-error
#12 1.975   
#12 1.975   × git clone --filter=blob:none --quiet 'ssh://****@github.com/taxintt/sloppy.git' /tmp/pip-install-gd771hcn/sloppy_7e4edbf65fe949d795d03e7ff53cb5a3 did not run successfully.
#12 1.975   │ exit code: 128
#12 1.975   ╰─> See above for output.
#12 1.975   
#12 1.975   note: This error originates from a subprocess, and is likely not a problem with pip.
#12 1.978 error: subprocess-exited-with-error
#12 1.978 
#12 1.978 × git clone --filter=blob:none --quiet 'ssh://****@github.com/taxintt/sloppy.git' /tmp/pip-install-gd771hcn/sloppy_7e4edbf65fe949d795d03e7ff53cb5a3 did not run successfully.
#12 1.978 │ exit code: 128
#12 1.978 ╰─> See above for output.
#12 1.978 
#12 1.978 note: This error originates from a subprocess, and is likely not a problem with pip.
------
failed to solve: rpc error: code = Unknown desc = executor failed running [/bin/sh -c pip install --no-cache-dir -r requirements.txt]: exit code: 1
make: *** [build-local] Error 17

一方で、Docker for Mac 4.7.1 (Engine: 20.10.14)では、--sshの option を指定した docker compose build を実行できます。

docker compose コマンドでの Buildkit の有効化は下記の blog を参考にできます。

https://www.docker.com/blog/faster-builds-in-compose-thanks-to-buildkit-support/

docker compose 側の --ssh の option は、基本的に --ssh default のように指定するだけです。

https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md#run---mounttypessh

make build-local         
env COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1        env UID=501 docker compose --project-directory=. --env-file=docker/local/compose.env build --ssh default --pull
[+] Building 14.5s (13/13) FINISHED                                                                                                                                                                              
 => [internal] load build definition from Dockerfile                                                                                                                                                        0.0s
 => => transferring dockerfile: 32B                                                                                                                                                                         0.0s
 => [internal] load .dockerignore                                                                                                                                                                           0.0s
 => => transferring context: 2B                                                                                                                                                                             0.0s
 => resolve image config for docker.io/docker/dockerfile:1                                                                                                                                                  0.8s
 => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:91f386bc3ae6cd5585fbd02f811e295b4a7020c23c7691d686830bf6233e91ad                                                                             0.0s
 => [internal] load build definition from Dockerfile                                                                                                                                                        0.0s
 => [internal] load .dockerignore                                                                                                                                                                           0.0s
 => [internal] load metadata for docker.io/library/python:3.9-buster                                                                                                                                        0.7s
 => [internal] load build context                                                                                                                                                                           0.0s
 => => transferring context: 38B                                                                                                                                                                            0.0s
 => [libs 1/4] FROM docker.io/library/python:3.9-buster@sha256:2ebfde37127af1b36196a6e4ca9b159e30b0fbea80b49cd1201ae48d907aeac4                                                                             0.0s
 => CACHED [libs 2/4] COPY requirements.txt .                                                                                                                                                               0.0s
 => CACHED [libs 3/4] RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts                                                                                                           0.0s
 => [libs 4/4] RUN --mount=type=ssh pip install --no-cache-dir -r requirements.txt                                                                                                                         12.6s
 => exporting to image                                                                                                                                                                                      0.1s
 => => exporting layers                                                                                                                                                                                     0.1s
 => => writing image sha256:feab17f4b00d2933673893742a0614cb431532148b89599722d5629577d45c04                                                                                                                0.0s 
 => => naming to docker.io/library/use_sloppy_app                                                                                                                                                           0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

一連の実行コマンド的には、下記のように Buildkit の有効化と --ssh オプションを指定するのみです。
(上記のログでは、検証環境に合わせた形で Makefile を利用しているので、下記のコマンドとは厳密には一致していません。)

$ export COMPOSE_DOCKER_CLI_BUILD=1 
$ export DOCKER_BUILDKIT=1 
$ docker compose build --ssh default

参考リンク

Discussion