Dockerfile 实战:把第一个 Node 服务容器化

零、这篇文章讲什么

前几篇用的都是官方现成镜像(nginxmysql),那是别人写好的 Dockerfile 构建的。这一篇轮到自己写:把我们之前跑在宿主机 1314 端口的 Node 服务,打包成自己的镜像 ,走完 Dockerfile → build → run → 验证 的完整链路。

读完你会得到四样东西:

  1. 一份逐行讲透的 Dockerfile
  2. "层与缓存"这个必考概念
  3. 一次真实的排错实录:国内镜像源 EOF(我踩过的坑)
  4. 镜像存哪、为什么 docker rmi 会拒绝(删除的正确顺序)

前置知识:第 01 篇的 image / container 概念。会跑 docker builddocker run 即可。

一、为什么需要 Dockerfile

第 01 篇说 image 是"光盘"。那么光盘是怎么造出来的? 答案是 Dockerfile------一份"如何构建镜像"的说明书。

arduino 复制代码
源码(index.js / package.json)
    │  docker build -t node-demo .
    ▼
Dockerfile 逐条指令 → 镜像(node-demo)
    │  docker run
    ▼
容器(node-app)

在项目根目录新建一个文件,名字就叫 Dockerfile(无扩展名),内容:

dockerfile 复制代码
FROM node:22          # 基础镜像:node 22 + linux + npm,即"运行环境"
WORKDIR /app          # 容器内的工作目录(不存在会自动创建)
COPY . .              # 把当前目录的源码拷进容器的 /app
EXPOSE 1314           # 声明容器内端口(只是声明,不负责映射)
CMD ["node", "index.js"]   # 容器启动时执行的命令

逐行解释:

指令 干什么 类比
FROM node:22 基于官方 node 镜像起步 买一个"node 环境"的半成品
WORKDIR /app 设定容器内工作目录 进到厨房的"案板"位置
COPY . . 拷源码进容器 把食材摆上案板
EXPOSE 1314 声明应用监听端口 告诉别人"我的菜是热乎的"
CMD [...] 容器启动执行的命令 端上桌的动作

我的 index.js 只用 Node 内置模块、没有第三方依赖,所以不用 npm install。如果项目有依赖,正确姿势是先 COPY package.json ./RUN npm install 最后 COPY . .------原因见下一节。

二、层与缓存(必考概念)

Dockerfile 里每条指令 = 构建出的一层,层之间像 git 提交一样做增量缓存:

  • 某条指令的输入没变,这一层直接复用缓存,不用重跑
  • 所以把"经常变的"放后面:源码天天改,依赖很少变
dockerfile 复制代码
# 错误的顺序:源码一变,依赖跟着全部重装
COPY . .
RUN npm install

# 正确的顺序:先锁依赖,再进源码
COPY package.json ./
RUN npm install          # 只有 package.json 变了才重装
COPY . .

这就是"缓存友好"的 Dockerfile。项目大了之后,这个顺序能让每次构建从分钟级降到秒级。

三、build → run → 验证

1. 构建镜像

bash 复制代码
docker build -t node-demo .

-t node-demo 给镜像起名,末尾的 . 是构建上下文(当前目录)。预期看到一段分步日志,最后:

ruby 复制代码
=> [1/3] FROM docker.io/library/node:22
=> [2/3] WORKDIR /app
=> [3/3] COPY . .
=> naming to docker.io/library/node-demo:latest done   ← 镜像造好了

2. 运行容器

bash 复制代码
docker run -d --name node-app -p 1314:1314 node-demo
docker ps

-p 1314:1314:宿主机 1314 → 容器内 1314。docker ps 能看到端口列 0.0.0.0:1314->1314/tcp

3. 验证

bash 复制代码
curl http://localhost:1314     # 应返回 hello world
docker logs node-app           # 应看到 node service run on 1314
docker exec -it node-app bash  # 进容器,ls /app 能看到你的 index.js

到这里,你的第一个镜像链路就完整了:源码 → build → image → run → container → 验证。

四、排错实录:国内镜像源 EOF

第一次 docker build,我卡在第一步,报错:

vbscript 复制代码
ERROR: failed to solve: node:22: failed to resolve source metadata
for docker.io/library/node:22: failed to do request: Head
"https://docker.mirrors.ustc.edu.cn/v2/library/node/manifests/22": EOF

报错末尾的 EOF 是线索------构建根本没开始,是拉基础镜像失败。原因:我机器配了镜像加速器,但中科大(ustc)和网易(163)这两个源已经失效了,而我的网络又直连不了 Docker Hub。

排查和修复三步:

bash 复制代码
# 1. 测各镜像源连通性(curl 返回 401 表示服务端可达,000 表示不通)
curl -sS -o /dev/null -w "%{http_code}\n" https://docker.mirrors.ustc.edu.cn/v2/   # 000 失效
curl -sS -o /dev/null -w "%{http_code}\n" https://docker.1ms.run/v2/               # 401 可用
json 复制代码
// 2. 改 daemon.json 里的 registry-mirrors,换成可用的源
// Windows 路径:C:\Users\<你>\.docker\daemon.json
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.m.daocloud.io"
  ]
}
bash 复制代码
# 3. 重启 Docker Desktop 让配置生效,再重新 build
docker desktop restart

这也是个运维实战经验:国内 Docker 加速器经常悄悄失效 ,遇到 EOF / failed to resolve source metadata 报错,第一反应就是"检查镜像源"。

五、镜像存哪 & 为什么 rmi 会拒绝

镜像不在你的项目文件夹里

docker images 能看到 node-demo,但项目目录里什么都没有------因为镜像不是普通文件,是 Docker 引擎管理的库存。你的项目目录只有"配方"(Dockerfile)和"原料"(源码),成品在 Docker 自己的存储区里:

  • Windows 上就是 WSL2 虚拟机的虚拟磁盘(ext4.vhdx),Windows 资源管理器看不到
  • docker images / docker ps 是你看到 Docker 内部世界的唯一窗口

想导出成普通文件,用快照:

bash 复制代码
docker save -o node-demo.tar node-demo   # 镜像 → tar 文件,可以传给别人
docker load -i node-demo.tar             # 别人收到后导入

为什么 docker rmi 会拒绝

我删除时踩了个经典的坑:

vbnet 复制代码
docker rmi node-demo
Error: conflict: unable to delete node-demo:latest (must be forced)
- container da9adcc6d068 is using its referenced image 9b1a8ed86bdf

因为容器 node-app 正用着这个镜像在跑。 Docker 的保护机制拒绝删除被引用的镜像。正确顺序是先删容器、再删镜像:

bash 复制代码
docker rm -f node-app    # ① 先删容器(镜像的下游)
docker rmi node-demo     # ② 没有引用者了,能删

顺序记忆法:谁引用谁,先删被引用的下游。容器引用镜像 → 先删容器,再删镜像。 报错提示的 -f 强制删除不是日常做法,会留下悬空引用。

六、小结

知识点 一句话
Dockerfile 一条指令一层,构建镜像的说明书
层与缓存 把"经常变的"放后面,命中缓存秒级构建
build/run -t 起名构建,-p 端口映射运行,logs 看日志
镜像源排错 EOF 先查加速器,curl 测 401 即通
镜像删除 先删容器,再删镜像,别用 -f

下一篇 05 两个容器协作 做这次练习的升级版------把 node 和 nginx 都容器化,放进同一个 docker 网络,让 nginx 用容器名反代到 node,完成真正的"两个容器协作"。

相关推荐
2301_810730101 小时前
K8s的Pod管理
linux·容器·kubernetes
川石课堂软件测试1 小时前
自动化测试常见的异常处理
python·jmeter·mysql·docker·容器·单元测试·grafana
CJY6211 小时前
k8s控制器管理
云原生·容器·kubernetes
无念是……2 小时前
K8s 控制器管理详解|RC、RS、Deployment、StatefulSet、DaemonSet、Job&CronJob
云原生·容器·kubernetes·控制器
EterNity_TiMe_2 小时前
一个网页管理多种远程连接:Docker 部署 Next Terminal 与资产审计
运维·人工智能·docker·ai·容器
mohesashou2 小时前
k8s的pod管理
云原生·容器·kubernetes
蒲锘2 小时前
DevOps 实验阶段四:Infra 节点 + MySQL + Spring Boot 容器化
spring boot·mysql·docker
WThh2 小时前
k8s中的控制器管理
云原生·容器·kubernetes
养海绵宝宝的小蜗3 小时前
【Kubernetes系列】控制器(Controller)全解:ReplicaSet、Deployment、DaemonSet、Job 与 CronJob
云原生·容器·kubernetes