如何从利用 npm 工作区的 mono-repo 容器化单节点应用程序

问题描述 投票:0回答:2

我有一个包含 4 个节点包的单一存储库:

/
└── apps
    ├── app1 ( "dependencies": { "lib1": "../../libs/lib1" } )
    └── app2 ( "dependencies": { "lib2": "../../libs/lib2", "express": "^4.18.2" } )
└── libs
    └── lib1  ( "dependencies": { "lib2": "../lib2", "puppeteer": "^21.1.1" } )
    └── lib2  ( "dependencies": { "node-gyp": "^9.4.0" } )
└── node_modules
└── package.json ( { ... "workspaces": ["apps/app*", "libs/lib*"], ... } )

如您所见:

  • app1
    不需要
    express
  • app2
    不需要
    lib1
    puppeteer

我想为

app1
app2
创建 Docker 镜像,但我不想包含该镜像实际上不需要的任何 node_modules,而且我也不想包含该镜像所不需要的任何其他目录或文件。不需要图像。

本质上我有两个问题:

  • 对于每个应用程序,Docker 映像应仅包含应用程序实际需要的 node_modules。
  • 某些依赖项只是同一项目中另一个模块的符号链接。因此,这些符号链接要么必须递归解析并直接包含到 node_modules 目录中,要么这些目录也需要复制到 Docker 映像中......但仅限于实际需要的目录。

目前,我只是将整个 mono-repo 复制到图像中:

FROM node:20.5.1-alpine3.18
RUN apk update && apk --no-cache add --virtual builds-deps build-base python3
ADD . /mono-repo
WORKDIR /mono-repo/apps/app2

我可以像这样列出所有依赖项吗?

npm ls -p -w ./apps/app2 | sed 1d

...然后以某种方式神奇地删除其他所有内容?


不确定是否相关,但我正在使用 GitLab CI,并且我有一个安装 node_modules 的作业,将它们放入缓存中,然后我有一个 docker-build 作业,它从缓存中检索 node_modules 以节省时间。因此,如果可能的话,我想继续使用该缓存以节省时间。每个 CI 作业都使用基于 alpine 的 Docker 镜像,我正在构建的镜像也基于 alpine。

编辑: 我现在在 GitLab 中使用正确的缓存(缓存存储在 ./.npm)。因此,我可以通过专门为一个工作区安装 node_modules 来解决问题之一:

npm ci --cache .npm --prefer-offline -w "$NODE_APP_DIR"

我还更新了 Dockerfile 以分两步运行(首先作为构建器安装依赖项并删除不必要的文件),然后我只需将剩下的所有内容复制到最终映像中:

# Stage 1: Create an intermediate image for npm install
FROM node:20.5.1-alpine3.18 as builder

# Install build dependencies
RUN apk update && apk --no-cache add build-base python3

# Copy the application code into the builder image
COPY . /mono-repo

# Set the working directory
WORKDIR /mono-repo

# Set the NODE_APP_DIR ARG
ARG NODE_APP_DIR

# Run npm install in the workspace
RUN npm ci --cache .npm --prefer-offline -w "$NODE_APP_DIR"

# Remove everything that the app doesn't need
RUN export EXCLUDE_PATHS="$(npm ls -p -w "$NODE_APP_DIR" | sed 1d)" && echo "DEPENDENCIES: $EXCLUDE_PATHS" && find "$PWD" -name "*" | grep -vE "$(echo -en "$EXCLUDE_PATHS" | sed 's/[]\/$*.^|[]/\\&/g' | sed 's/^/^/' | tr '\n' '|')"

# Stage 2: Create the final production image
FROM node:20.5.1-alpine3.18

# Copy everything that's left from the builder image
COPY --from=builder /mono-repo /mono-repo

# Set the working directory
WORKDIR /mono-repo

# Set the NODE_APP_DIR ARG
ARG NODE_APP_DIR

# CMD for the final image
CMD npm start --workspace "$NODE_APP_DIR"

docker build --build-args NODE_APP_DIR="apps/app2" -t app2:test

不幸的是,这不太有效。具体来说,我尝试删除应用程序不需要的所有内容的部分:

RUN export EXCLUDE_PATHS="$(npm ls -p -w "$NODE_APP_DIR" | sed 1d)" && echo "DEPENDENCIES: $EXCLUDE_PATHS" && find "$PWD" -name "*" | grep -vE "$(echo -en "$EXCLUDE_PATHS" | sed 's/[]\/$*.^|[]/\\&/g' | sed 's/^/^/' | tr '\n' '|')"

首先,我使用

npm ls -p -w "$NODE_APP_DIR"
获取应用程序具有的所有依赖项(递归地)的文件路径列表。然后我使用
find
递归地查找 mono-repo 中的每个文件。然后,我使用 grep 过滤掉以 npm ls 命令返回的路径之一开头的所有路径。为了使其正常工作,我必须首先使用 sed 清理依赖项列表,以便我可以将其与正则表达式一起使用。然后我在每行(正则表达式)的开头添加一个插入符号,然后用管道(正则表达式)替换所有换行符,以使 grep 与任何依赖项匹配。

但是看来我的想法行不通。尽管我专门只为一个应用程序安装了节点模块,但它似乎仍然返回了大量的node_modules。难道就没有更好的办法了吗?整个方法感觉太老套了......

node.js docker npm dockerfile sh
2个回答
0
投票

据我所知,您的方法是正确的。您可以改进排除不需要的文件的方法,可以像下面这样进行优化。这将确保使用 npm 和 docker 的多阶段构建机制仅将所需的依赖项添加到最终的 docker 文件中。

参考下面代码中的

Docker
文件:

FROM node:20.5.1-alpine3.18 AS builder
RUN apk update && apk --no-cache add build-base python3
COPY . /mono-repo
WORKDIR /mono-repo
ARG NODE_APP_DIR
RUN npm ci --cache .npm --prefer-offline -w "$NODE_APP_DIR"
RUN mkdir /app && cp -R "$NODE_APP_DIR" /app && cd /app && npm prune --production
FROM node:20.5.1-alpine3.18
COPY --from=builder /app /app
WORKDIR /app
ARG NODE_APP_DIR
CMD npm start --workspace "$NODE_APP_DIR"

上面将复制整个 Mono 存储库,并使用

npm ci
安装依赖项,然后将文件复制到新目录
/app
,然后将相关应用程序代码复制到创建的目录。最后在
npm prune --production
目录中运行
/app
将删除不必要的node_modules。

然后仅将修剪后的

/app
目录从构建器复制到舞台到最终图像。

将工作目录设置为

/app
并确保设置
NODE_APP_DIR
在您的情况下它可能是
apps/app2

然后您可以使用以下命令为

app2
构建 docker 镜像:

docker build --build-arg NODE_APP_DIR="apps/app2" -t app2:test .

希望这有帮助。如果有任何问题请随时询问。


0
投票

如果我不得不猜测,您缺少的是嵌套的

packages/package.json
文件。我认为不那么老套的东西肯定是合适的。

这是一篇 Medium 文章,演示如何使用 npm 工作区。总结一下这篇文章:

  1. 使用单独的 package.json 文件标记您的包
  2. 通过
    workspaces
  3. 将它们包含在顶级 package.json 文件中
  4. 使用
    npm link
    进行本地测试和开发(听起来与您所描述的类似)
  5. 为每个刚刚构建/包含这些包的包创建 Dockerfiles

这听起来像是可以完成您想要做的事情吗?

© www.soinside.com 2019 - 2024. All rights reserved.