Docker容器化Node.js应用教程

从零开始:用Docker容器化你的第一个Node.js应用

1. 引言

在现代软件开发中,Docker已成为不可或缺的工具。它通过容器化技术,将应用及其依赖环境打包在一起,实现了"一次构建,处处运行"的梦想。今天,我将带大家完成一个完整的Docker实践:容器化一个简单的Node.js应用,从编写代码到运行容器,一步步掌握Docker的核心操作。

2. 项目准备

首先,我们创建一个简单的Node.js项目,仅包含两个文件:

2.1. 应用代码 (index.js)

javascript 复制代码
console.log("你好,世界!")

这是一个最简单的Node.js程序,输出一句问候语。

2.2. Docker镜像定义文件 (Dockerfile)

dockerfile 复制代码

让我们解析一下这个Dockerfile:

  • FROM node:14-alpine:指定基础镜像为Node.js 14的Alpine Linux版本,这是一个轻量级的Linux发行版
  • COPY index.js /index.js:将本地的index.js文件复制到容器内的根目录
  • CMD node /index.js:设置容器启动时默认执行的命令

3. 构建Docker镜像

有了Dockerfile,我们就可以创建自定义的Docker镜像了。

3.1. 构建命令

powershell 复制代码
docker build -t hello-docker .

这个命令的含义是:

  • docker build:启动镜像构建过程
  • -t hello-docker:为镜像打上标签(名称),便于后续引用
  • .:使用当前目录作为构建上下文,Docker会查找当前目录下的Dockerfile

3.2. 构建日志

powershell 复制代码
PS E:\hello-world\hello-docker> docker build -t hello-docker .
[+] Building 369.5s (7/7) FINISHED                                                                            docker:desktop-linux
 => [internal] load build definition from Dockerfile                                                                          0.1s
 => => transferring dockerfile: 103B                                                                                          0.0s
 => [internal] load metadata for docker.io/library/node:14-alpine                                                           263.8s
 => [internal] load .dockerignore                                                                                             0.0s
 => => transferring context: 2B                                                                                               0.0s
 => [internal] load build context                                                                                             0.1s
 => => transferring context: 29B                                                                                              0.0s
 => [1/2] FROM docker.io/library/node:14-alpine@sha256:434215b487a329c9e867202ff89e704d3a75e554822e07f3e0c0f9e606121b33     103.1s
 => => resolve docker.io/library/node:14-alpine@sha256:434215b487a329c9e867202ff89e704d3a75e554822e07f3e0c0f9e606121b33       0.1s
 => => sha256:434215b487a329c9e867202ff89e704d3a75e554822e07f3e0c0f9e606121b33 1.43kB / 1.43kB                                0.0s
 => => sha256:4e84c956cd276af9ed14a8b2939a734364c2b0042485e90e1b97175e73dfd548 1.16kB / 1.16kB                                0.0s
 => => sha256:0dac3dc27b1ad570e6c3a7f7cd29e88e7130ff0cad31b2ec5a0f222fbe971bdb 6.44kB / 6.44kB                                0.0s 
 => => sha256:0dac3dc27b1ad570e6c3a7f7cd29e88e7130ff0cad31b2ec5a0f222fbe971bdb 6.44kB / 6.44kB                                0.0s 
 => => sha256:f56be85fc22e46face30e2c3de3f7fe7c15f8fd7c4e5add29d7f64b87abdaa09 3.37MB / 3.37MB                               63.4s 
 => => sha256:8f665685b215c7daf9164545f1bbdd74d800af77d0d267db31fe0345c0c8fb8b 37.17MB / 37.17MB                             90.6s 
 => => sha256:e5fca6c395a62ec277102af9e5283f6edb43b3e4f20f798e3ce7e425be226ba6 2.37MB / 2.37MB                               52.7s 
 => => sha256:561cb69653d56a9725be56e02128e4e96fb434a8b4b4decf2bdeb479a225feaf 448B / 448B                                   95.7s 
 => => extracting sha256:f56be85fc22e46face30e2c3de3f7fe7c15f8fd7c4e5add29d7f64b87abdaa09                                     0.6s 
 => => extracting sha256:8f665685b215c7daf9164545f1bbdd74d800af77d0d267db31fe0345c0c8fb8b                                    11.2s 
 => => extracting sha256:e5fca6c395a62ec277102af9e5283f6edb43b3e4f20f798e3ce7e425be226ba6                                     0.3s 
 => => extracting sha256:561cb69653d56a9725be56e02128e4e96fb434a8b4b4decf2bdeb479a225feaf                                     0.0s 
 => [2/2] COPY index.js /index.js                                                                                             2.0s 
 => exporting to image                                                                                                        0.2s 
 => => exporting layers                                                                                                       0.1s 
 => => writing image sha256:be987f77136525684c13002b3a8d43cc6cd4d62986f7997128b51946aad6d03c                                  0.0s 
 => => naming to docker.io/library/hello-docker                                                                               0.0s 

View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/pnwg2u6n5xocvatlj87a6v6fj

 1 warning found (use docker --debug to expand):
 - JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals (line 3)

3.3. 构建过程解析

  1. Docker首先下载基础镜像node:14-alpine(如果本地不存在)
  2. 然后执行COPY指令,将index.js文件复制到镜像中
  3. 最后设置默认的启动命令
  4. 构建完成后,生成了一个ID为be987f771365的新镜像

这里Docker给出了一个有用的警告:建议使用JSON格式的CMD指令来避免信号处理问题。虽然不影响当前使用,但在生产环境中需要注意这一点。

4. 查看本地镜像

构建完成后,我们可以查看本地已有的Docker镜像:

powershell 复制代码
docker images

日志输出:

powershell 复制代码
PS E:\hello-world\hello-docker> docker images
REPOSITORY                       TAG       IMAGE ID       CREATED         SIZE  
hello-docker                     latest    be987f771365   8 minutes ago   119MB 
hello-world                      latest    74cc54e27dc4   10 months ago   10.1kB
hub.oepkgs.net/openeuler/nginx   <none>    1d992e662bfc   13 months ago   747MB

输出显示我们刚刚创建的hello-docker镜像大小为119MB,相比基础镜像已经很小了。同时可以看到系统中还有其他镜像,比如经典的hello-world测试镜像。

5. 运行容器

镜像只是静态的模板,要运行应用需要从镜像创建容器:

powershell 复制代码
docker run hello-docker

日志输出:

powershell 复制代码
PS E:\hello-world\hello-docker> docker run hello-docker
你好,世界!

执行后,终端立即输出了"你好,世界!"。这意味着:

  1. Docker从hello-docker镜像创建了一个新容器
  2. 容器内执行了node /index.js命令
  3. Node.js解释器执行了我们的代码,输出结果到终端
  4. 程序执行完毕,容器自动停止

6. 查看容器

powershell 复制代码
PS E:\hello-world\hello-docker> docker ps   
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
PS E:\hello-world\hello-docker> 
powershell 复制代码
PS E:\hello-world\hello-docker> docker ps -a
CONTAINER ID   IMAGE          COMMAND                   CREATED          STATUS                      PORTS     NAMES
9fadd3ad3798   hello-docker   "docker-entrypoint.s..."   54 seconds ago   Exited (0) 52 seconds ago             affectionate_burnell

运行docker ps查看当前正在运行的容器,发现没有结果。这是因为我们的应用执行完就退出了,容器也随之停止。

但使用docker ps -a查看所有容器(包括已停止的),可以看到刚刚运行的容器:

  • 状态为"Exited (0)",表示正常退出
  • 54秒前创建,52秒前退出
  • 有一个随机生成的名字"affectionate_burnell"

这就是容器与虚拟机的关键区别之一:容器是为了运行特定进程而存在的,当进程结束,容器的使命也就完成了。

7. 实践总结

通过这个简单的实践,我们完成了Docker的核心工作流:

  1. 编写应用代码 → 2. 定义Dockerfile → 3. 构建镜像 → 4. 运行容器

在这个过程中,我们学到了几个关键点:

  • 镜像与容器的关系:镜像是静态的模板,容器是镜像的运行实例
  • 分层构建:Docker镜像采用分层结构,每一条指令都会创建一个新的层
  • 轻量级特性:容器随内部进程的结束而停止,资源占用更高效
  • 环境一致性:无论在哪里运行,容器内部环境都完全相同
相关推荐
CappuccinoRose1 小时前
Docker配置过程完整梳理
后端·python·docker·容器·环境配置
草明1 小时前
MacOS 在使用 docker: no space left on device: unknown
macos·docker
vipbic12 小时前
解决npm publish的404/403和配置警告全记录
前端·npm·node.js
晨晖215 小时前
安装node.js,使用vue的准备环境
node.js
求梦82016 小时前
Java:Windows家庭中文版的Docker下载安装
java·windows·docker
大帅子17 小时前
Mac 用户对于不同前端项目自动配置 node 版版本
前端·node.js
❀͜͡傀儡师17 小时前
docker一键部署Flatnotes笔记工具
笔记·docker·容器
❀͜͡傀儡师17 小时前
docker一键部署夜莺监控
运维·docker·容器
程序员爱钓鱼18 小时前
Node.js 编程实战:深入掌握异步性能优化
后端·node.js·trae