Docker
构建 Docker 镜像是一种部署各种应用程序的常用方法。然而,从 monorepo 中执行此操作会带来一些挑战。
须知:
本指南假定你正在使用 create-turbo 或具有类似结构的仓库。问题
在 monorepo 中,不相关的更改可能会导致 Docker 在部署你的应用时执行不必要的工作。
假设你有一个如下所示的 monorepo
你想使用 Docker 部署 apps/api,因此你创建了一个 Dockerfile
这将复制根目录的 package.json 和根目录的 lockfile 到 Docker 镜像。然后,它将安装依赖项,复制应用源代码并启动应用。
你还应该创建一个 .dockerignore 文件,以防止将 node_modules 与应用的源代码一起复制进来。
lockfile 更改过于频繁
Docker 在部署你的应用方面非常智能。就像 Turborepo 一样,它会尽量 减少工作量。
在我们的 Dockerfile 示例中,只有当镜像中的文件与上次运行时不同时,它才会运行 npm install。否则,它将恢复之前拥有的 node_modules 目录。
这意味着,每当 package.json、apps/api/package.json 或 package-lock.json 更改时,Docker 镜像都会运行 npm install。
这听起来很棒 - 直到我们意识到一些事情。package-lock.json 对于 monorepo 来说是全局的。这意味着,**如果我们在 apps/web 中安装一个新包,我们将导致 apps/api 重新部署**。
在一个大型 monorepo 中,这可能会导致大量时间浪费,因为对 monorepo 的 lockfile 的任何更改都会级联到数十甚至数百个部署中。
解决方案
解决方案是修剪 Dockerfile 的输入,使其仅包含绝对必要的内容。Turborepo 提供了一个简单的解决方案 - turbo prune。
运行此命令会在 ./out 目录中创建一个 **修剪版本的 monorepo**。它仅包含 api 依赖的工作区。它还会**修剪 lockfile**,以便仅下载相关的 node_modules。
--docker 标志
默认情况下,turbo prune 将所有相关文件放在 ./out 中。但为了优化 Docker 的缓存,我们理想情况下希望分两个阶段复制文件。
首先,我们只想复制安装包所需的文件。当运行 --docker 时,你会在 ./out/json 中找到这些文件。
之后,你可以复制 ./out/full 中的文件以添加源文件。
以这种方式拆分**依赖项**和**源文件**使我们能够**仅在依赖项更改时运行 npm install** - 从而大大加快速度。
如果不使用 --docker,则所有修剪后的文件都将放置在 ./out 中。
示例
我们详细的 with-docker 示例深入探讨了如何充分利用 prune。以下是 Dockerfile,为方便起见复制过来。
从 monorepo 的根目录构建 Dockerfile
远程缓存
为了在 Docker 构建期间利用远程缓存,你需要确保你的构建容器具有访问你的远程缓存的凭据。
有很多方法可以在 Docker 镜像中处理密钥。我们将在此处使用一个简单的策略,使用多阶段构建,将密钥作为构建参数,这些参数将在最终镜像中隐藏。
假设你正在使用类似于上面 Dockerfile 的文件,我们将在 turbo build 之前从构建参数中引入一些环境变量
turbo 现在将能够访问你的远程缓存。要查看非缓存 Docker 构建镜像的 Turborepo 缓存命中,请从你的项目根目录运行如下命令