Docker
构建 Docker 镜像是部署各种应用程序的常见方式。然而,在单体仓库中这样做有几个挑战。
Good to know:
This guide assumes you're using create-turbo or a repository with a similar structure.问题
在单体仓库中,不相关的更改可能会导致 Docker 在部署应用程序时做不必要的工作。
假设你有一个如下所示的单体仓库:
你想使用 Docker 部署 apps/api
,所以你创建了一个 Dockerfile:
这将把根目录的 package.json
和根锁文件复制到 Docker 镜像中。然后,它会安装依赖项,复制应用程序源代码并启动应用程序。
你还应该创建一个 .dockerignore
文件,以防止 node_modules 被复制到应用程序的源代码中。
锁文件变化太频繁
Docker 在部署应用程序时相当智能。就像 Turborepo 一样,它尝试做尽可能少的工作。
在我们的 Dockerfile 中,只有当它在镜像中的文件与上一次运行不同时,才会运行 npm install
。如果没有变化,它将恢复之前的 node_modules
目录。
这意味着每当 package.json
、apps/api/package.json
或 package-lock.json
发生变化时,Docker 镜像将运行 npm install
。
这听起来很好 - 直到我们意识到一个问题。package-lock.json
对于单体仓库来说是全局性的。这意味着如果我们在 apps/web
中安装一个新包,我们将导致 apps/api
重新部署。
在一个大型单体仓库中,这可能会导致大量时间浪费,因为单体仓库锁文件的任何更改都会级联到数十或数百个部署中。
解决方案
解决方案是将 Dockerfile 的输入修剪为仅包含严格必要的内容。Turborepo 提供了一个简单的解决方案 - turbo prune
。
运行此命令会在 ./out
目录中创建单体仓库的修剪版本。它只包括 api
依赖的工作区。它还修剪锁文件,使得只有相关的 node_modules
会被下载。
--docker
标志
默认情况下,turbo prune
将所有相关文件放在 ./out
中。但为了优化 Docker 的缓存,我们理想情况下希望分两个阶段复制文件。
首先,我们只想复制安装包所需的内容。当运行 --docker
时,你会在 ./out/json
中找到这些内容。
之后,你可以复制 ./out/full
中的文件来添加源文件。
以这种方式分离依赖项和源文件让我们只在依赖项更改时运行 npm install
- 给我们带来更大的加速。
不使用 --docker
时,所有修剪后的文件都会放在 ./out
中。
示例
我们详细的 with-docker
示例深入探讨了如何充分利用 prune
。为方便起见,这里是 Dockerfile。
从你的单体仓库的根目录构建 Dockerfile:
远程缓存
要在 Docker 构建期间利用远程缓存,你需要确保你的构建容器有访问你的远程缓存的凭证。
在 Docker 镜像中处理机密有很多方法。这里我们将使用多阶段构建的简单策略,使用构建参数作为机密,这些参数在最终镜像中会被隐藏。
假设你使用的 Dockerfile 类似于上面的那个,我们将在 turbo build
之前从构建参数中引入一些环境变量:
turbo
现在将能够访问你的远程缓存。要查看非缓存 Docker 构建镜像的 Turborepo 缓存命中,从项目根目录运行如下命令: