我正在创建一个可以构建 haskell 程序的 Dockerfile。 Dockerfile使用ubuntu focus作为基础镜像,安装ghcup,然后构建haskell程序。我这样做的原因有很多;它可以支持低配置的 CI 环境,并且可以帮助尝试构建复杂项目的新开发人员。
为了加快构建时间,我使用 docker v20 和 buildkit。我有一系列这样的事件(这是一个很长的文件,但此摘录是相关部分):
# installs haskell
WORKDIR $HOME
RUN git clone https://github.com/haskell/ghcup-hs.git
WORKDIR ghcup-hs
RUN BOOTSTRAP_HASKELL_NONINTERACTIVE=NO ./bootstrap-haskell
#RUN source ~/.ghcup/env # Uh-oh: can't do this.
# We recreate the contents of ~/.ghcup/env
ENV PATH=$HOME/.cabal/bin:$HOME/.ghcup/bin:$PATH
# builds application
COPY application $HOME/application
WORKDIR $HOME/application
RUN mkdir -p logs
RUN --mount=type=cache,target=$HOME/.cabal \
--mount=type=cache,target=$HOME/.ghcup \
--mount=type=cache,target=$HOME/application/dist-newstyle \
cabal build |& tee logs/configure.log
但是当我在
application
中更改一些非代码文件(例如 README.md),并构建我的 docker 映像时...
DOCKER_BUILDKIT=1 docker build -t application/application:1.0 .
...这需要相当多的时间,并且
cabal build
的输出包括大量 Downloading [blah]
以及来自 cabal 安装的 Building
/Installing
/Completed
消息。
但是,当我进入容器并输入
cabal build
时,速度要快得多(它已经构建好了):
host$ docker run -it application/application:1.0
container$ cabal build # this is fast
我希望它在之前的情况下也能一样快。由于我并没有真正更改代码文件,并且依赖项都已下载,而且由于我使用的是
RUN --mount
。
是否有我的
--mount=type=cache
条目未覆盖的文件?是否有一个包注册表文件需要包含在其自己的 --mount=type=cache
行中?据我所知,我的构建应该几乎是即时的,而不是需要几分钟才能完成。
几年后,但我想我已经有了答案。
因此 OP 方法的问题之一是,在这种情况下 $HOME 不会被任何东西取代。因此,缓存的目标目录包括一个字面意思为
$HOME
的文件夹。
下一步是获取正确的文件夹,您可以通过检查使用“错误”配置生成的图像来完成此操作。如果你使用像dive这样的工具,你可以看到该层的变化包括:
drwx------ 0:0 14 MB └── root
drwxr-xr-x 0:0 461 kB ├── .cache
drwxr-xr-x 0:0 461 kB │ └── cabal
drwxr-xr-x 0:0 11 kB │ ├── logs
-rw-r--r-- 0:0 485 B │ │ ├── build.log
drwxr-xr-x 0:0 11 kB │ │ └── ghc-9.8.4
-rw-r--r-- 0:0 11 kB │ │ └── text-2.1.2-030cac37f8d77dcf6263d580f
drwxr-xr-x 0:0 450 kB │ └── packages
drwxr-xr-x 0:0 450 kB │ └── hackage.haskell.org
drwxr-xr-x 0:0 450 kB │ └── text
drwxr-xr-x 0:0 450 kB │ └── 2.1.2
-rw-r--r-- 0:0 450 kB │ └── text-2.1.2.tar.gz
drwxr-xr-x 0:0 13 MB └── .local
drwxr-xr-x 0:0 13 MB └── state
drwxr-xr-x 0:0 13 MB └── cabal
drwxr-xr-x 0:0 13 MB └── store
drwxr-xr-x 0:0 13 MB └── ghc-9.8.4-1b19
drwxr-xr-x 0:0 0 B ├── incoming
-rw-r--r-- 0:0 0 B │ └── text-2.1.2-030cac37f8d77dcf6
drwxr-xr-x 0:0 14 kB ├── package.db
-rw-r--r-- 0:0 8.6 kB │ ├── package.cache
-rw-r--r-- 0:0 0 B │ ├── package.cache.lock
-rw-r--r-- 0:0 5.1 kB │ └── text-2.1.2-030cac37f8d77dcf6
drwxr-xr-x 0:0 13 MB └── text-2.1.2-030cac37f8d77dcf6263d
-rw-r--r-- 0:0 497 B ├── cabal-hash.txt
drwxr-xr-x 0:0 13 MB ├── lib
drwxr-xr-x 0:0 2.5 MB │ ├── Data
drwxr-xr-x 0:0 2.1 MB │ │ ├── Text
-rw-r--r-- 0:0 12 kB │ │ │ ├── Array.dyn_hi
-rw-r--r-- 0:0 12 kB │ │ │ ├── Array.hi
(more changes omitted)
通过这个我们可以看到文件被缓存的实际位置是
/root/.cache/cabal
和/root/.local/state/cabal/store
。 或者至少,在 GHC 和 cabal-install 的配置中就是这种情况,在这个例子中,我使用 haskell:9.8.4
作为基础镜像。
考虑到这一切,这里是一个完整的、多阶段的 Dockerfile,它利用构建缓存(可执行文件称为
example
):
FROM haskell:9.8.4 as builder
WORKDIR /app
COPY . .
RUN --mount=type=cache,target=/root/.local/state/cabal/store \
--mount=type=cache,target=/root/.cache/cabal \
--mount=type=cache,target=./dist-newstyle \
cabal update && \
mkdir bin && \
cabal install --install-method=copy --installdir=./bin
FROM debian:12-slim
COPY --from=builder /app/bin/example /usr/local/bin/
CMD ["example"]