使用turborepo创建纱线1.22 monorepos的开发Dockerfile和Docker-Compose.yml。
#node #docker #monorepo #turborepo

如果您曾经在一个纱线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