如果您曾经在一个纱线Monorepo项目中与相互依赖的工作区一起工作,那么您知道以避免不必要的重建方式创建用于开发的Docker容器可能是一个挑战。
Turborepo,“ ...针对JavaScript和Typescript代码库进行了优化的智能构建系统”提供了使此任务更容易的工具。
让我们假设一个看起来像这样的项目:
- /project-directory
- /apps
- /frontend
- package.json
- other files...
- /backend
- package.json
- other files...
- /packages
- /shared-stuff
- package.json
- other files...
- .dockerignore
- package.json
- turbo.json
- Dockerfile.dev
- docker-compose.yml
.dockerignore
**/node_modules
**/.next
**/dist
这可以确保将node_modules和构建工件从我们的dockerfile中的主机复制到容器时被排除在外。
package.json
{
"name": "turbo-docker-monorepo",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"packageManager": "yarn@1.22.19",
"devDependencies": {
"turbo": "^1.9.6"
}
}
这是最小的软件包。json您需要使用纱线Monorepo,该纱线使用“应用程序”和“软件包”目录中的工作空间使用TurborePo。没什么可说的!
turbo.json
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
}
}
}
此Turbo.json文件取自the basic turborepo example。
让我们假设backend
应用具有packages/shared-stuff
工作空间作为依赖项(含义后端软件包。json在其依赖项列表中具有"shared-stuff": "*"
)。
我们可以使用turborepo以尊重其对shared-stuff
软件包的依赖的方式来构建后端,命令turbo run build --filter=backend
将(按顺序)构建shared-stuff
package,然后构建backend
app。酷!
dockerfile.dev
# syntax=docker/dockerfile:1.5.2
# based on: https://github.com/vercel/turbo/blob/main/examples/with-docker/apps/api/Dockerfile
FROM node:20.2-alpine3.17 as base
# adding apk deps to avoid node-gyp related errors and some other stuff. adds turborepo globally
RUN apk add -f --update --no-cache --virtual .gyp nano bash libc6-compat python3 make g++ \
&& yarn global add turbo \
&& apk del .gyp
#############################################
FROM base AS pruned
WORKDIR /app
ARG APP
COPY . .
# see https://turbo.build/repo/docs/reference/command-line-reference#turbo-prune---scopetarget
RUN turbo prune --scope=$APP --docker
#############################################
FROM base AS installer
WORKDIR /app
ARG APP
COPY --from=pruned /app/out/json/ .
COPY --from=pruned /app/out/yarn.lock /app/yarn.lock
# Forces the layer to recreate if the app's package.json changes
COPY apps/${APP}/package.json /app/apps/${APP}/package.json
# see https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#run---mounttypecache
RUN \
--mount=type=cache,target=/usr/local/share/.cache/yarn/v6,sharing=locked \
yarn --prefer-offline --frozen-lockfile
COPY --from=pruned /app/out/full/ .
COPY turbo.json turbo.json
# For example: `--filter=frontend^...` means all of frontend's dependencies will be built, but not the frontend app itself (which we don't need to do for dev environment)
RUN turbo run build --no-cache --filter=${APP}^...
# re-running yarn ensures that dependencies between workspaces are linked correctly
RUN \
--mount=type=cache,target=/usr/local/share/.cache/yarn/v6,sharing=locked \
yarn --prefer-offline --frozen-lockfile
#############################################
FROM base AS runner
WORKDIR /app
ARG APP
ARG START_COMMAND=dev
COPY --from=installer /app .
CMD yarn workspace ${APP} ${START_COMMAND}
让我们介绍每一层中发生的事情。对于以下讨论,假设正在创建backend
App服务,该服务将shared-stuff
作为依赖关系。
根据
FROM node:20.2-alpine3.17 as base
RUN apk add -f --update --no-cache --virtual .gyp nano bash libc6-compat python3 make g++ \
&& yarn global add turbo \
&& apk del .gyp
该图层添加了容器依赖性,以确保正确构建使用节点GYP的NPM模块。它还在全球添加了Turborepo。其余的层是由“基础”
构建的修剪
FROM base AS pruned
WORKDIR /app
ARG APP
COPY . .
RUN turbo prune --scope=$APP --docker
该层在项目文件上复制并运行有关服务的turbo prune。 $ app参数将是“前端”或“后端”(假设为以下示例为示例),该示例设置在docker-compose.yml中(最后将介绍)。关于--docker
标志:
使用Docker标志,Prune命令将生成以下内容的文件夹:
- 带有修剪工作空间的package.jsons的文件夹json
- 一个包含修剪工作空间的完整源代码的文件夹,但仅包括构建目标所需的内部软件包。
- 一个新的修剪锁紧file,仅包含原始根锁定的修剪子集,其依赖项实际上由修剪工作空间中的软件包使用。
假设我们正在构建backend
服务容器,这是此时修剪层中的“ OUT”目录的样子:
- /out
- /full
- /apps
- /backend (all files)
- /packages
- /shared-stuff (all files)
- .gitignore
- package.json
- turbo.json
- /json
- /apps
- /backend
- package.json
- /packages
- /shared-stuff
- package.json
- package.json
- yarn.lock
请注意,“前端”应用不存在,并且(根据TurborePo文档)其依赖项被排除在yarn.lock
之外,这是我们想要的!
安装程序
FROM base AS installer
WORKDIR /app
ARG APP
# COPY 1
COPY --from=pruned /app/out/json/ .
COPY --from=pruned /app/out/yarn.lock /app/yarn.lock
COPY apps/${APP}/package.json /app/apps/${APP}/package.json
# RUN 1
RUN \
--mount=type=cache,target=/usr/local/share/.cache/yarn/v6,sharing=locked \
yarn --prefer-offline --frozen-lockfile
# COPY 2
COPY --from=pruned /app/out/full/ .
COPY turbo.json turbo.json
# RUN 2
RUN turbo run build --no-cache --filter=${APP}^...
# RUN 3
RUN \
--mount=type=cache,target=/usr/local/share/.cache/yarn/v6,sharing=locked \
yarn --prefer-offline --frozen-lockfile
复制1 命令系列开始从修剪的层复制json
中的json
目录,以及“ scoped” yarn.lock文件。它还冗余地复制了应用程序的软件包。主机的json,我发现该容器需要在共享workspave依赖项更改时正确重新创建。
第一个运行命令(运行1)运行yarn
并使用cache mount针对纱线cache目录的cache mount,这将有助于加快随后的重建。
接下来的两个语句(副本2)从pruned
层以及主机的turbo复制out/full
的内容。
由于该容器将在开发环境中运行,因此我们可以安全地跳过构建backend
应用程序。但是,我们确实需要构建packages/shared-stuff
,在当前示例中是backend
的依赖性。这是通过运行2 中的命令完成的:RUN turbo run build --no-cache --filter=${APP}^...
。在这种情况下,由于我们正在构建后端服务容器,因此过滤器标志将解析为--filter=backend^...
。这意味着所有后端的工作空间依赖性(而不是后端本身)都将是我们想要的。
最终运行命令(运行3)只是第一个(运行1)的副本。最终的yarn
呼叫确保工作区之间的依赖关系正确地链接
跑步者
FROM base AS runner
WORKDIR /app
ARG APP
ARG START_COMMAND=dev
COPY --from=installer /app .
CMD yarn workspace ${APP} ${START_COMMAND}
最后一层在安装程序层上复制并运行命令以启动应用程序(基于START_COMMAND
参数)。
现在让我们看一下docker-compose.yml,特别是backend
服务的配置(几乎完成!)
version: '3.8'
x-defaults:
&defaults
init: true
tty: true
networks:
- my_monorepo_network
networks:
my_monorepo_network:
services:
backend:
<<: *defaults
ports:
- "3333:3333"
command: yarn workspace backend dev
environment:
- PORT=3333
build:
args:
APP: backend
START_COMMAND: dev
context: .
dockerfile: ./Dockerfile.dev
volumes:
- ./apps/backend:/app/apps/backend
- /app/node_modules
- /app/apps/backend/node_modules
...other services...
这是节点服务的非常典型的定义。在这里,您可以看到在Dockerfile.dev中使用的构建args。
这就是本教程的全部。请务必查看the example repo!