Docker
构建 Docker 镜像是一种常见的部署各种应用程序的方式。然而,从单体仓库执行此操作会面临一些挑战。
在单体仓库中,不相关的更改可能会在部署应用程序时使 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
。
默认情况下,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 缓存命中,请从你的项目根目录运行如下命令