资讯详情

Docker容器化实战:用NodeJS构建镜像并部署运行

📅 2026/10/20 7:25:02 | 华诺云谱 👁 阅读
Docker容器化实战:用NodeJS构建镜像并部署运行
Docker刚出来那两年很多人觉得它是“运维才需要玩的东西”搞业务开发的碰都不碰。但只要你试过在一台新电脑上把NodeJS项目跑起来装完Node、再配环境变量、再发现版本不对、再发现某个原生依赖编译失败……折腾一个下午之后你会无比渴望有一个东西能把这些破事全部打包隔离。Docker就是干这个的。这篇内容不搞那种很玄的理论轰炸而是从一个实际需求出发给你一个NodeJS程序我们从零开始装Docker、写镜像配置、构建镜像、把程序跑起来。整个过程我尽量按照实际操作的时间顺序来讲什么地方容易踩坑、什么地方可以偷懒我都会指出来。这套流程你要是走通了后面玩转数据库、Redis、Nginx这些容器化部署基本都是一通百通。1. 动手之前先别急着敲命令把三个核心概念搞清楚很多人学Docker失败不是命令敲得少而是对着教程复制粘贴了半天完全不知道自己在干什么。所以哪怕你是急着上手的性子我也建议你先用五分钟把下面几件事捋明白这比多敲十遍命令都管用。1.1 镜像和容器一个像“安装包”一个像“运行中的程序”这个概念我每次都会用生活里的例子来讲。你把镜像理解成手机里的“安装包”容器就是“已经安装好并且正在运行的App”。安装包是静态的躺在那里不能干活容器是动态的它跑起来了占资源会出日志可以被停止、删除。所以整个Docker的工作流就是先准备好一个镜像然后基于这个镜像去创建并运行容器。我们要做的NodeJS程序最终跑起来的时候就是以“容器”的形式存在的。这个理解一旦建立后面看到docker run就不会懵了——它的意思其实就是“基于某个安装包把App启动起来”。1.2 镜像的分层结构为什么Docker能这么省空间镜像还有一个关键特性它是一层一层堆叠起来的。每个Dockerfile指令都会生成一个新层Docker会缓存这些层。这也是为什么你在自己电脑上构建过的镜像再次构建时会非常快连同一个基础镜像的多个不同项目也可以共享底层文件而不是各存一份。这对NodeJS项目直接的影响就是基础镜像的层是共享的你只需要把项目代码那一层打进去。所以不要动不动就去下载一个几百MB的所谓“全家桶镜像”尽量选体积小、适合跑生产的基础镜像比如node:20-alpine。这也是后面实操里我会优先选的类型。1.3 镜像仓库Docker世界的“应用商店”镜像不会凭空产生你第一步需要一个基础镜像比如NodeJS官方提供的镜像。这些镜像放在一个叫“镜像仓库”的地方最知名的是Docker Hub上面放着海量官方和社区维护的镜像。用docker pull命令可以把远程的镜像拉到本地。但对于国内环境来说直接访问Docker Hub有时候会比较慢这就涉及镜像加速的配置。这是个非常实际的体验问题我后面会专门讲一下配置方法免得你卡在第一步。1.4 用Docker跑NodeJS的完整链路把概念串起来我们要做的事就非常清晰了在电脑上安装好Docker环境。准备一个最简单的NodeJS程序含代码和依赖描述文件。编写一个Dockerfile告诉Docker怎么把我们的程序变成镜像。执行docker build构建出项目镜像。执行docker run基于镜像启动容器让NodeJS服务在容器里跑起来。通过浏览器或命令访问这个服务验证整个链路通了。后面所有内容都是顺着这六步展开的你心里有一条线就不容易在细节里迷路。2. 安装Docker不同系统上的操作差异安装Docker本身不复杂但不同操作系统上的安装方式和后续权限管理不太一样这里分开说。2.1 Linux系统上安装以Ubuntu为例Linux下的安装主流方式是用官方提供的脚本一条命令搞定curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh这个脚本会自动检测你的发行版然后把Docker引擎装好。装完之后Docker是以root权限运行的所以每次敲docker命令都需要加sudo。为了方便日常操作你可以把当前用户加入docker组之后就不用在每条命令前面敲sudo了sudo usermod -aG docker $USER newgrp docker注意这一操作有个安全风险加入docker组后你的用户权限几乎等同于root因为Docker本身可以挂载宿主机目录。自家开发机这么用没问题公司服务器上要慎重。提示装完之后立刻检查一下执行docker version看到Client和Server两段都正常输出说明引擎在运行。如果只有Client有信息、Server报错多半是Docker守护进程没起来Linux下用sudo systemctl enable --now docker启动一下。2.2 macOS和Windows上的安装macOS和Windows推荐直接安装官方桌面版。这个软件包自带Docker引擎、命令行工具以及一个图形化管理界面对新手非常友好。安装过程基本就是一路下一步但有一个关键前置条件Windows上必须开启硬件虚拟化一般BIOS里默认打开并且需要确保Windows的“虚拟机平台”功能处于启用状态。很多人装完死活启动不了回头一看就是这一步漏了。macOS相对省心只要是近几年的机器直接装就行。这两个平台的桌面版安装完成后菜单栏或任务栏会出现一个小鲸鱼图标点击后看到Engine running就说明环境已经就绪。2.3 安装后的三个验证动作不管哪个平台装完我都建议做一个“最小化验证”确认环——境是通的docker version # 查看客户端和服务端版本 docker info # 查看运行环境详细信息 docker run hello-world # 镜像运行最小化测试第三条命令会拉取一个官方测试镜像然后打印一段说明文字。如果这段文字能正常打印出来说明你拉取镜像、创建容器、运行容器整个链路都没有问题。很多人直接跳过这个测试开始搞业务最后遇到问题了才想起来排查环境反而浪费时间。我自己的习惯是装完立刻跑一遍这三条确认环境的“地基”是稳的再往上盖楼。3. 准备一个NodeJS服务并编写Dockerfile现在进入正题假设我们要把一个NodeJS HTTP服务打进Docker里。3.1 先准备一个最简单的NodeJS程序为了演示我们写一个极简的HTTP服务。新建一个项目文件夹比如就叫docker-node-demo在里面创建app.jsconst express require(express); const app express(); const port 3000; app.get(/, (req, res) { res.send(Hello from Dockerized NodeJS!); }); app.listen(port, () { console.log(App listening at http://localhost:${port}); });再创建package.json{ name: docker-node-demo, version: 1.0.0, main: app.js, scripts: { start: node app.js }, dependencies: { express: ^4.18.2 } }本地如果没有装NodeJS也没关系我们最终的目的是让这个程序在Docker容器里运行。但写Dockerfile之前我建议你至少知道这段代码是干什么用的——它会监听3000端口并返回一行文本。3.2 Dockerfile逐行拆解在项目文件夹里新建一个名为Dockerfile的文件注意没有后缀内容如下FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm install --registryhttps://registry.npm.taobao.org COPY . . EXPOSE 3000 CMD [npm, start]一行一行来看。FROM node:20-alpine指定基础镜像。20是Node主版本alpine表示这是一个基于 Alpine Linux 的极简版本体积要比默认的node:20小很多下载更快跑起来也更省内存。如果项目里有用到比较冷门的原生模块可能需要换回标准版但绝大多数项目用alpine都没问题。WORKDIR /app设置镜像里的工作目录。后面所有相对路径都会基于这个目录。这相当于在容器里创建了一个/app文件夹并把它作为我们的“项目根目录”。COPY package*.json ./先把依赖描述文件复制进去。注意这里复制的是package.json和package-lock.json如果有。单独把这两个文件先复制进去是有讲究的——Docker的每一层都有缓存只要这一层的输入没变构建时就直接用缓存不会再执行一次。如果我们在代码改动之后仍然能命中依赖层的缓存构建速度会快非常多。RUN npm install --registryhttps://registry.npm.taobao.org在镜像里安装依赖。我特意加了一个 registry 参数替换成国内可用的npm镜像源这样下载依赖的速度会有质的飞跃。你自己操作的时候也可以把这个镜像源地址换成自己用着顺手的。COPY . .把整个项目目录里的文件复制进镜像覆盖到刚才的/app目录下。前面已经单独复制过依赖文件并安装了依赖这里再把代码复制进去相当于“代码变、依赖不变”的缓存效果。EXPOSE 3000声明容器内服务使用的端口。注意它只是“声明”并不会真的把端口映射到宿主机。真正让外部访问到服务需要在docker run时用-p参数做端口映射。CMD [npm, start]设置容器启动时默认执行的命令。这里用了数组格式它直接执行npm这个可执行程序参数是start。一个镜像只能有一个CMD谁最后写谁生效。3.3 .dockerignore一个很容易被忽略但能救命的文件如果你的项目里恰好装了node_modules并且没有.dockerignore文件那么执行COPY . .时会把本地那一堆依赖包一起塞进镜像里。这不仅让镜像体积膨胀而且在Linux和Windows的环境差异下本地node_modules里可能包含不适合容器运行的二进制文件最终结果就是镜像构建成功但容器运行时报各种奇怪的错。解决方案非常简单。在项目里新建一个.dockerignore文件内容类似这样node_modules .git *.log .DS_Store这样COPY . .时就会自动忽略这些目录。无论是镜像体积还是构建速度都会明显改善。这一步属于“做一次受益终身”的习惯。4. 构建镜像、加载并运行容器到这里所有准备工作做完了。下面进入真正让Docker“干活”的阶段。4.1 用docker build构建项目镜像在项目文件夹里执行docker build -t docker-node-demo:v1 .解释一下参数-t docker-node-demo:v1给镜像命名并打标签。docker-node-demo是仓库名v1是标签通常用来标识版本。命名和标签可以随便起最好是语义化比如myproject:20250101。最后的.指定构建上下文路径。这个.代表当前目录Docker会把当前目录里没有被.dockerignore忽略的文件打包发送给Docker守护进程作为构建上下文。第一次构建会拉取NodeJS基础镜像这时候能明显感觉到网络的影响。如果拉取卡住大概率是Docker Hub访问慢建议先配置镜像加速器然后再试。构建过程中你会看到一步步执行FROM、WORKDIR、COPY、RUN、CMD的输出。每一步完成右上角会出现类似[OK]的提示表示这一层构建成功。构建完成后用docker images查看本地镜像列表能看到刚才构建出来的镜像通常体积在200MB上下。4.2 用docker run加载并运行容器镜像构建好之后运行容器只需要一条命令docker run -d -p 8080:3000 --name my-node-app docker-node-demo:v1参数拆开看-d后台运行容器不占用当前终端。-p 8080:3000端口映射。格式是“宿主机端口:容器内端口”。这里的意思是把容器内的3000端口映射到宿主机的8080端口。--name my-node-app给容器起一个名字方便后续管理。最后是镜像名也就是我们刚构建好的docker-node-demo:v1。执行完后用docker ps查看运行中的容器。只要看到my-node-app在列表里说明容器已经正常起来了。然后在浏览器访问http://localhost:8080就能看到 “Hello from Dockerized NodeJS!” 这行字。注意容器内部的服务监听的是3000端口也就是我们代码里app.listen(port)的port这个值没有变。所以-p 8080:3000的右侧要跟代码里监听的端口保持一致。如果把右侧写成了8080服务在容器里监听3000映射就失效了。4.3 查看日志和进入容器排查问题程序如果在容器里崩了或者根本没起来第一件要做的事就是看日志docker logs my-node-app日志里会显示NodeJS进程打出的所有输出包括我们的App listening at...以及任何报错信息。如果发现容器没有任何输出多半是启动命令出了问题或者npm install阶段就失败了。有时候日志看不出明显问题但你怀疑容器内部环境不对。这时候可以进入容器查看现场docker exec -it my-node-app shexec是在运行中的容器里再执行一个命令-it表示用交互式终端操作sh是启动一个交互式shell。进入容器后你可以像登录一台服务器一样到处看看检查文件有没有复制进去、依赖有没有装上、环境变量对不对。这个实操习惯在排查问题时会帮你节省大量时间。很多新手只会反复改代码重新构建却不愿意进入容器里看一眼实际状态结果绕大圈。5. 常见问题与排查技巧实录下面这些问题是我实际带人做的时候遇到的高频问题整理成速查表会直观一些。现象常见原因解决思路构建时拉取基础镜像卡住Docker Hub网络延迟高配置镜像加速器重试docker build容器启动后立即退出启动命令报错或进程主动退出执行docker logs查看运行日志端口访问不通端口映射写错或服务监听地址错误检查-p格式代码中监听0.0.0.0而非127.0.0.1npm install 非常慢默认下载源网络慢Dockerfile中指定国内npm镜像源镜像体积异常大项目依赖被误复制进镜像确认.dockerignore是否生效docker: permission denied当前用户不在docker组usermod -aG docker $USER后重新登录构建时能安装依赖运行时报模块找不到package.json和lock文件不一致删除本地lock后重新build或确保lock文件被正确复制5.1 端口冲突怎么办如果本地的8080端口已经被其他程序占用运行docker run时会报地址占用错误。两个解决方向换一个宿主机端口比如-p 8081:3000或者先检查并停掉占用端口的进程。我自己更推荐前者因为更简单也避免了误杀其他服务的风险。5.2 镜像构建慢的问题怎么破构建慢主要分两块基础镜像下载慢和npm依赖安装慢。前者通过配置镜像加速器解决后者通过在Dockerfile里指定国内的npm镜像源解决。这两个慢的问题如果不处理新手很容易以为是自己操作错了反复重装Docker实际上跟Docker本身无关。5.3 容器起来了但访问不到服务这个问题最隐蔽。很多人按教程把容器跑起来了docker ps也显示正常但浏览器访问就是打不开。如果端口映射没有问题那十有八九是NodeJS服务监听的地址不对。在容器里代码用app.listen(port)监听默认是监听所有网络接口的这没问题但如果你在代码里显式写了app.listen(port, 127.0.0.1)那服务只会监听容器内部地址宿主机代理过来的流量进不来。写NodeJS服务的时候监听地址不要硬编码成127.0.0.1。5.4 如何自己排查“莫名挂掉”的容器容器如果不断重启你可以先看一眼重启次数docker ps -a看到STATUS列有Restarting字样说明容器确实在崩溃循环里。这时候不要再反复docker run先跑docker logs把错误信息拿出来。绝大多数崩溃问题日志里都会直接写清楚原因剩下的交给排查思路就行。6. 把单个容器进阶成“服务编排”一点经验之谈如果你把上面的NodeJS服务跑通了你会发现一个事实Docker本身并不难难的是用Docker的时候怎么管理这些容器、网络、存储和依赖关系。一个真实的业务项目往往不只一个服务前端、后端、数据库、缓存中间件……它们之间需要相互通信端口要合理分配环境变量要集中管理。这时候再用一条条docker run去启动容器效率太低了而且特别容易记混参数。我的建议是把这篇入门内容做完之后下一站直接学习docker-compose。你在项目里写一个docker-compose.yml把所有服务的镜像、端口、环境变量、数据卷都声明好然后一条docker-compose up -d就把整套环境拉起来。你会在配置里用到一个叫“网络”的东西服务之间用“服务名”互相访问不需要再记一堆IP地址。我个人在实际操作中的体会是Docker入门阶段的关键不在于命令背得熟而在于建立“镜像—容器—仓库”“构建—运行—排查”这条完整链路的心智模型。一旦你脑子里有了这条链路所有新概念都可以挂到熟悉的位置上——数据卷解决持久化、容器编排解决多服务协同、镜像构建优化解决交付效率。慢慢你会发现Docker不是给运维专用的工具而是每一个写代码的人都应该具备的基本功。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑