如何使用Node.js与Docker – CodesCode

了解在Docker容器中运行Node.js应用程序的好处以及如何创建实用的开发工作流程

本教程解释了在Docker容器中运行Node.js应用程序的好处,以及如何创建实用的开发工作流程。

Node.js允许您使用服务器端和客户端的JavaScript创建快速和可扩展的Web应用程序。您的应用程序在开发机上可能运行完美,但您能确保它在同事的设备或生产服务器上运行吗?

考虑以下情况:

  • 您可能使用的是macOS,而其他人使用的是Windows,服务器运行的是Linux。
  • 您安装了Node.js 20,但其他人使用各种运行时版本。
  • 您正在使用可能在其他平台上有差异或不可用的数据库等依赖项。
  • 您确定您的新代码在另一个操作系统(OS)上不能执行任何危险操作吗?

Docker提供了解决方案

Docker有助于解决上述列出的“但在我的机器上可以工作”的问题。您不需要在本地安装应用程序,而是在一个轻量级的隔离虚拟机环境中运行它,这个环境被称为容器

Docker

真实的虚拟机模拟PC硬件,因此您可以安装操作系统。Docker模拟操作系统,所以您可以安装应用程序。通常每个基于Linux的容器只安装一个应用程序,并通过虚拟网络连接它们,以便它们可以在HTTP端口上进行通信。

优势:

  • 您的Docker设置可以模拟生产Linux服务器,或者您可以使用容器进行部署。
  • 您可以在几分钟内下载、安装和配置依赖项。
  • 您的容器化应用程序完全相同地在所有设备上运行。
  • 更安全。您的应用程序可能会破坏容器的操作系统,但不会影响您的计算机,您可以在几秒钟内重新启动。

使用Docker,您无需在计算机上安装Node.js或使用诸如nvm之类的运行时管理选项。

您的第一个脚本

WindowsmacOSLinux上安装Docker Desktop,然后创建一个名为version.js的小脚本,其中包含以下代码:

console.log(`Node.js版本:${process.version}`);

如果您在本地安装了Node.js,请尝试运行该脚本。如果您安装了版本18,则会看到以下输出:

$ node version.jsNode.js版本:v18.18.2

现在,您可以在Docker容器中运行相同的脚本。下面的命令使用最新的长期支持(LTS)版本的Node.js。在脚本所在的目录中运行以下命令(仅限macOS或Linux):

$ docker run --rm --name version \  -v $PWD:/home/node/app \  -w /home/node/app \  node:lts-alpine version.jsNode.js版本:v20.9.0

Windows Powershell用户可以使用类似的命令,使用{}括在PWD周围:

> docker run --rm --name version -v ${PWD}:/home/node/app -w /home/node/app node:lts-alpine version.jsNode.js版本:v20.9.0

第一次运行可能需要一到两分钟的时间来执行,因为Docker在下载依赖项。后续运行是瞬间完成的。

让我们尝试不同版本的Node.js ——例如最新的 21 版本。在 macOS 或 Linux 上:

$ docker run --rm --name version \  -v $PWD:/home/node/app \  -w /home/node/app \  node:21-alpine version.jsNode.js 版本:v21.1.0

在 Windows Powershell 上:

> docker run --rm --name version -v ${PWD}:/home/node/app -w /home/node/app node:21-alpine version.jsNode.js 版本:v21.1.0

请记住,此脚本是在已安装特定版本的 Node.js 的 Linux 容器内运行的。

参数说明

好奇的话,以下是命令的参数说明:

  • docker run 从镜像启动一个新的容器 —— 关于镜像的更多信息将在下面介绍。

  • --rm 在终止容器时删除容器。如果没有必要重新启动容器,不需要保留它们。

  • --name version 给容器分配一个名称,以便更简单地管理。

  • -v $PWD:/home/node/app(或 -v ${PWD}:/home/node/app)绑定挂载一个卷。在这种情况下,主机PC上的当前目录被挂载到容器内的 /home/node/app

  • -w /home/node/app 设置 Node.js 的工作目录。

  • node:lts-alpine 是镜像的名称 —— 在这种情况下,它是运行在 Alpine Linux 中的 Node.js 的 LTS 版本。镜像包含了运行应用程序所需的操作系统和文件。可以将相同的镜像启动任意数量的容器:它们都引用相同的文件集,因此每个容器需要的资源很少。

  • version.js 是要执行的命令(从工作目录内执行)。

Docker镜像可以从 Docker Hub 获取,它们适用于包括 Node.js 在内的应用程序和运行时环境。镜像通常以标签的形式提供多个版本,例如 :lts-alpine20-bullseye-slim 或者 latest

需要注意的是,Alpine 是一个基本映像大小约为 5MB 的微型 Linux 发行版。它不包含很多库,但对于本教程中的简单项目来说已经足够。

运行复杂应用程序

上面的 version.js 脚本很简单,不包含任何依赖项或构建步骤。大多数 Node.js 应用程序使用 npmnode_modules 目录中安装和管理模块。无法使用上述命令的原因是:

  • 无法在主机PC上运行 npm (可能没有安装 Node.js 或者正确版本)。
  • 一些模块需要特定于平台的二进制文件。无法在主机PC上安装 Windows 二进制文件并期望它在 Linux 容器中运行。

解决方案是创建自己的 Docker 镜像,其中包含:

  • 适用于 Node.js 运行时的合适版本
  • 已安装了所有所需模块的应用程序版本

以下演示将使用 Express.js 框架构建一个简单的 Node.js 应用程序。创建一个名为 simple 的新目录,并在其中添加一个包含以下内容的 package.json 文件:

{  "name": "simple",  "version": "1.0.0",  "description": "simple Node.js and Docker example",  "type": "module",  "main": "index.js",  "scripts": {    "debug": "node --watch --inspect=0.0.0.0:9229 index.js",    "start": "node index.js"  },  "license": "MIT",  "dependencies": {    "express": "^4.18.2"  }}

添加一个名为 index.js 的文件,并包含以下 JavaScript 代码:

// 引入 Express 应用程序import express from 'express';// 配置常量const cfg = {  port: process.env.PORT || 3000};// 初始化 Expressconst app = express();// 主页路由app.get('/:name?', (req, res) => {  res.send(`你好 ${ req.params.name || '世界' }!`);});// 启动服务器app.listen(cfg.port, () => {  console.log(`服务器正在监听 http://localhost:${ cfg.port }`);});

请不要尝试在主机 PC 上安装依赖项或运行此应用程序!

创建一个名为 Dockerfile 的文件,并包含以下内容:

# 基于 Node.js LTS 的基础镜像FROM node:lts-alpine# 定义环境变量ENV HOME=/home/node/appENV NODE_ENV=productionENV NODE_PORT=3000# 创建应用程序文件夹并赋予 node 用户权限RUN mkdir -p $HOME && chown -R node:node $HOME# 设置工作目录WORKDIR $HOME# 设置活动用户为 node# 从主机复制 package.jsonCOPY --chown=node:node package.json $HOME/# 安装应用程序模块RUN npm install && npm cache clean --force# 复制剩余文件COPY --chown=node:node . .# 在主机上开放端口EXPOSE $NODE_PORT# 应用程序启动命令CMD [ "node", "./index.js" ]

这定义了安装和执行应用程序所需的步骤。注意,将 package.json 复制到镜像中,然后运行 npm install,最后复制剩余文件。这比一次性复制所有文件更有效,因为 Docker 在每个命令处创建一个镜像层。如果应用程序文件(index.js)发生更改,Docker 只需要运行最后三个步骤,而不需要再次运行 npm install

可选地,您可以添加一个名为 .dockerignore 的文件。它类似于 .gitignore,用于阻止 COPY . . 将不必要的文件复制到镜像中。例如:

Dockerfile.git.gitignore.vscodenode_modulesREADME.md

通过执行以下命令(注意末尾的 . 表示使用当前目录中的文件)构建名为 simple 的 Docker 镜像:

$ docker image build -t simple .

如果在上面使用的 node:lts-alpine Docker 镜像还未从系统中删除,镜像应在几秒内构建完成。

假设构建成功,从镜像启动一个容器:

$ docker run -it --rm --name simple -p 3000:3000 simple服务器正在监听 http://localhost:3000

-p 3000:3000<host-port> 发布或公开到 <container-port>,这样您主机上的端口 3000 将路由到容器内的端口 3000。

打开浏览器,输入网址 http://localhost:3000/,即可看到“你好世界!”

尝试向网址添加名称,例如 http://localhost:3000/Craig,以查看其他信息。

最后,通过在 Docker Desktop 的 容器(Containers) 选项卡中单击 停止 图标,或在另一个终端窗口中输入以下命令停止您的应用程序运行:

docker container stop simple

更好的 Docker 开发工作流程

上述过程存在一些令人沮丧的缺点:

  • 对代码(index.js 中的代码)进行任何更改,都需要停止容器、重新构建镜像、重新启动容器并重新测试。

  • 无法附加 Node.js 调试器,例如 VS Code 中提供的调试器。

通过保留现有的生产级别镜像并运行一个带有覆盖的容器,Docker 可以改进您的开发工作流程,以执行以下操作:

  • 设置环境变量,如将 NODE_ENV 设置为 development

  • 将本地目录挂载到容器中。

  • 使用 npm run debug 启动应用程序。此命令运行 node --watch --inspect=0.0.0.0:9229 index.js,在文件更改时重新启动应用程序(Node.js 18 中的新功能),并在允许来自容器外部的请求的情况下启动调试器。

  • 将应用程序端口 3000 和调试器端口 9229 暴露给主机。

  • </ul

    你可以用一个很长的docker run命令来做到这一点,但我更喜欢使用Docker Compose。它已经安装在Docker桌面版上,并且经常用于启动多个容器。创建一个名为docker-compose.yml的新文件,并添加以下内容:

    version: '3'  services:    simple:      environment:        - NODE_ENV=development      build:        context: ./        dockerfile: Dockerfile      container_name: simple      volumes:        - ./:/home/node/app      ports:        - "3000:3000"        - "9229:9229"      command: /bin/sh -c 'npm install && npm run debug'

    以调试模式启动应用程序:

    $ docker compose up[+] Building 0.0s[+] Running 2/2 ✔ Network simple_default  Created ✔ Container simple        CreatedAttaching to simplesimple  |simple  | up to date, audited 63 packages in 481mssimple  |simple  | > [email protected] debugsimple  | > node --watch --inspect=0.0.0.0:9229 index.jssimple  |simple  | Debugger listening on ws://0.0.0.0:9229/de201ceb-5d00-1234-8692-8916f5969cbasimple  | For help, see: https://nodejs.org/en/docs/inspectorsimple  | server listening at http://localhost:3000

    请注意,旧版的Docker Compose是使用docker-compose运行的Python脚本。新版本将Compose功能集成到主要可执行文件中,因此需要使用docker compose来运行。

    应用程序的实时重启

    打开index.js,进行一些修改(比如修改第14行的字符串),然后保存文件,即可自动重启应用程序:

    simple  | Restarting 'index.js'simple  | Debugger listening on ws://0.0.0.0:9229/acd16665-1399-4dbc-881a-8855ddf9d34csimple  | For help, see: https://nodejs.org/en/docs/inspectorsimple  | server listening at http://localhost:3000

    在浏览器中打开或刷新https://localhost:3000/以查看更新。

    使用VS Code进行调试

    打开VS Code的运行和调试面板,点击创建一个launch.json文件

    VS Code Run and Debug pane

    在下拉菜单中选择Node.js,将创建并打开一个.vscode/launch.json文件。添加以下代码,将调试器连接到运行的容器:

    {  // 用IntelliSense了解可能的属性。  // 悬停以查看现有属性的描述。  // 获取更多信息,请访问:https://go.microsoft.com/fwlink/?linkid=830387  "version": "0.2.0",  "configurations": [    {      "type": "node",      "request": "attach",      "name": "Attach to Container",      "address": "localhost",      "port": 9229,      "localRoot": "${workspaceFolder}",      "remoteRoot": "/home/node/app",      "skipFiles": [        "<node_internals>/**"      ]    }  ]}

    保存文件,然后在Debug面板的顶部点击Attach to Container开始调试。

    VS Code Run and Debug

    会出现一个调试工具栏。切换到index.js,并通过点击行号栏添加断点(显示一个红点)。

    set breakpoint in VS Code

    在浏览器中刷新https://localhost:3000/,VS Code将在断点处停止执行,并显示所有应用程序变量的状态。点击调试工具栏上的图标以继续运行、逐步执行代码或关闭调试器。

    停止容器

    通过打开另一个终端停止正在运行的容器。
    cd 到应用程序目录并输入:

    docker compose down

    摘要

    尽管 Docker 需要一些初始设置时间,但可靠且可分发的代码的长期好处远远超过了努力。
    当您添加其他依赖项(如数据库)时,Docker 变得无价。

    本教程解释了在 Docker 容器中运行 Node.js 应用程序的基础知识。要深入了解,请考虑以下 CodesCode 资源:

    分享本文


Leave a Reply

Your email address will not be published. Required fields are marked *