在开发和生产中使用 Docker for Node.js

2025-05-24

在开发和生产中使用 Docker for Node.js

我目前的主要技术栈是 Node.js/JavaScript,和许多团队一样,我将开发和生产环境迁移到了 Docker 容器中。然而,当我开始学习 Docker 时,我发现大多数文章都侧重于开发或生产环境,却找不到关于如何组织 Docker 配置以灵活应对这两种情况的文章。

在本文中,我将演示 Node.js Dockerfile 的不同用例和示例,解释决策过程,并帮助您设想如何使用 Docker 构建您的开发流程。我们将从一个简单的示例开始,然后回顾更复杂的场景和解决方法,以确保您在使用或不使用 Docker 的情况下都能获得一致的开发体验。

免责声明:本指南内容广泛,主要针对具有不同 Docker 技能水平的不同受众;在某些时候,所述说明对您来说显而易见,但我会尝试在它们旁边提出一些相关要点,以便提供对最终设置的完整概述。

先决条件

描述案例

  • 基本 Node.js Dockerfile 和 docker-compose
  • Nodemon 正在开发中,Node 正在生产中
  • 让生产 Docker 镜像远离 devDependecies
  • 使用多阶段构建镜像需要 node-gyp 支持

添加 .dockerignore 文件

在开始配置 Dockerfile 之前,我们先在 app 文件夹中添加一个 .dockerignore 文件。该文件会在 COPY/ADD 命令执行过程中排除文件中指定的文件。点击此处了解更多信息。



node_modules
npm-debug.log
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode


Enter fullscreen mode Exit fullscreen mode

基本 Node.js Dockerfile

为了确保理解清晰,我们将从可用于简单 Node.js 项目的基本 Dockerfile 开始。所谓“简单”,是指您的代码不需要任何额外的原生依赖或构建逻辑。



FROM node:10-alpine

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

CMD [ "npm", "start" ]


Enter fullscreen mode Exit fullscreen mode

你会在每篇 Node.js Docker 文章中找到类似的内容。让我们简单回顾一下。



WORKDIR /usr/src/app


Enter fullscreen mode Exit fullscreen mode

workdir 是一种默认目录,用于执行任何 RUN、CMD、ENTRYPOINT、COPY 和 ADD 指令。在某些文章中,你会看到有人执行 mkdir /app 并将其设置为 workdir,但这并非最佳实践。使用预先存在的 /usr/src/app 文件夹更适合此操作。



COPY package*.json ./
RUN npm install


Enter fullscreen mode Exit fullscreen mode

另一个最佳实践调整是:在将代码复制到容器之前,先复制 package.json 和 package-lock.json 文件。Docker 会将已安装的 node_modules 缓存为单独的层,这样,如果您更改了应用程序代码并执行了构建命令,而之前没有更改过 package.json 文件,则不会再次安装 node_modules。一般来说,即使您忘记添加这两行,也不会遇到太多问题。通常情况下,只有在 package.json 文件发生更改时才需要运行 docker build,但这会导致您不得不从头开始安装。在其他情况下,在开发环境中完成初始构建后,您不需要过于频繁地运行 docker build。

docker-compose 出现的时刻

在开始在生产环境中运行我们的应用程序之前,我们必须对其进行开发。编排和运行 Docker 环境的最佳方式是使用docker-compose。在 YAML 文件中,以易于使用的语法定义要运行的容器/服务列表及其说明,以便进一步运行。



version: '3'

services:
  example-service:
    build: .
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    ports:
      - 3000:3000
      - 9229:9229
    command: npm start


Enter fullscreen mode Exit fullscreen mode

在上面这个基本的 docker-compose.yaml 配置示例中,构建过程使用应用程序文件夹中的 Dockerfile 完成,然后将应用程序文件夹挂载到容器中,并且构建期间安装在容器内的 node_modules 不会被当前文件夹覆盖。假设您正在运行 Web 服务器,则 3000 端口将暴露给您的本地主机。9229 用于暴露调试端口。点击此处了解更多信息

现在运行你的应用程序:



docker-compose up


Enter fullscreen mode Exit fullscreen mode

或者使用 VS 代码扩展来实现相同的目的。

使用此命令,我们将 Dockerized 应用程序的 3000 和 9229 端口公开到 localhost,然后将包含应用程序的当前文件夹挂载到 /usr/src/app,并使用 hack 来防止通过 Docker 从本地机器覆盖节点模块。

那么,你能在开发和生产环境中使用该 Dockerfile 吗?
是也不是。

CMD 的区别
首先,通常你希望开发环境中的应用程序在文件更改时重新加载。为此,你可以使用nodemon。但在生产环境中,你希望不使用 nodemon 运行。这意味着开发环境和生产环境的 CMD(命令)必须不同。

对此有几种不同的选择:

1.将 CMD 替换为不使用 nodemon 运行应用程序的命令,该命令可以是 package.json 文件中单独定义的命令,例如:



 "scripts": {
   "start": "nodemon --inspect=0.0.0.0 src/index.js",
   "start:prod": "node src/index.js"
 }


Enter fullscreen mode Exit fullscreen mode

在这种情况下,你的 Dockerfile 可能是这样的:



FROM node:10-alpine

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

CMD [ "npm", “run”, "start:prod" ]


Enter fullscreen mode Exit fullscreen mode

但是,由于您在开发环境中使用 docker-compose 文件,因此我们可以在里面使用不同的命令,就像前面的示例一样:



version: '3'

services:
   ### ... previous instructions
    command: npm start


Enter fullscreen mode Exit fullscreen mode

2.如果差异较大,或者开发和生产环境都使用 docker-compose,可以根据差异创建多个 docker-compose 文件或 Dockerfile。例如 docker-compose.dev.yml 或 Dockerfile.dev。

管理软件包安装
通常,最好保持生产镜像尽可能小,并且不要安装生产环境不需要的 Node 模块依赖项。通过维护一个统一的 Dockerfile,仍然可以解决这个问题。

重新检查你的 package.json 文件,并将 devDependencies 与依赖项分开。点击此处了解更多信息。简而言之,如果你在 npm install 中使用 --production 标志运行,或者将 NODE_ENV 设置为 production,则所有 devDependencies 都不会被安装。我们将在 docker 文件中添加一些额外的代码来解决这个问题:



FROM node:10-alpine

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

CMD [ "npm", “run”, "start:prod" ]


Enter fullscreen mode Exit fullscreen mode

为了定制我们使用的行为



ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}


Enter fullscreen mode Exit fullscreen mode

Docker 支持通过 docker 命令或 docker-compose 传递构建参数。默认情况下,NODE_ENV=development 会被使用,除非我们用其他值覆盖它。您可以在这里找到更详细的解释。

现在,当您使用 docker-compose 文件构建容器时,所有依赖项都会被安装。当您为生产环境构建容器时,您可以将 build 参数设置为 production,这样 devDependencies 就会被忽略。因为我使用 CI 服务来构建容器,所以我只需在其配置中添加该选项即可。点击此处了解更多信息

对需要 node-gyp 支持的图像使用多阶段构建
并非每个尝试在 Docker 中运行的应用程序都会专门使用 JS 依赖项,其中一些需要使用 node-gyp 和额外安装的本机操作系统库。

为了解决这个问题,我们可以使用多阶段构建,它可以帮助我们在单独的容器中安装和构建所有依赖项,并仅将安装结果(不包含任何垃圾)移动到最终容器中。Dockerfile 可能如下所示:



# The instructions for the first stage
FROM node:10-alpine as builder

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

RUN apk --no-cache add python make g++

COPY package*.json ./
RUN npm install

# The instructions for second stage
FROM node:10-alpine

WORKDIR /usr/src/app
COPY --from=builder node_modules node_modules

COPY . .

CMD [ "npm", “run”, "start:prod" ]


Enter fullscreen mode Exit fullscreen mode

在该示例中,我们在第一阶段根据环境安装并编译了所有依赖项,然后在第二阶段复制了我们将在开发和生产环境中使用的 node_modules。

该行RUN apk --no-cache add python make g++可能因项目而异,可能是因为您需要额外的依赖项。



COPY --from=builder node_modules node_modules


Enter fullscreen mode Exit fullscreen mode

在这一行中,我们将第一阶段的 node_modules 文件夹复制到第二阶段的 node_modules 文件夹。因此,在第二阶段,我们将 WORKDIR 设置为 /usr/src/app,这样 node_modules 就会复制到该文件夹​​。

概括

我希望本指南能帮助您了解如何组织 Dockerfile,并使其满足开发和生产环境的需求。我们的建议总结如下:

  • 尝试统一开发和生产环境的 Dockerfile;如果不行,就将它们拆分。
  • 不要为生产版本安装 dev node_modules。
  • 不要在最终图像中留下 node-gyp 和 node 模块安装所需的本机扩展依赖项。
  • 使用 docker-compose 来协调您的开发设置。
  • 在生产中,您可以选择什么来编排您的 Docker 容器,可以是 docker-compose、Docker Swarm 或 Kubernetes。
文章来源:https://dev.to/alex_barashkov/using-docker-for-nodejs-in-development-and-products-3cgp
PREV
2020 年部署 Web 应用的 4 种方法
NEXT
十大开源 SaaS 助你打造下一个大热门