从零跑通 Docker:镜像、容器到 Node.js 项目部署实战源码
Docker 新手入门指南|附源码
在现代软件开发中,Docker 已经成为后端开发、运维部署、微服务架构、持续集成与持续交付(CI/CD)中非常重要的基础工具。对于新手来说,Docker 可能一开始看起来有些抽象:什么是镜像?什么是容器?为什么项目跑得好好的,还要用 Docker?本文将从零开始,带你理解 Docker 的核心概念,并通过一个完整示例,演示如何使用 Docker 构建、运行和部署一个简单项目。
本文适合以下人群阅读:
- 刚接触 Docker 的开发者;
- 想把本地项目容器化的后端工程师;
- 需要了解基础部署流程的前端或测试人员;
- 准备学习微服务、Kubernetes、DevOps 的初学者。
一、什么是 Docker?
Docker 是一个开源的容器化平台,它可以把应用程序以及应用所依赖的运行环境、配置文件、系统库等内容打包在一起,形成一个独立的运行单元,这个运行单元通常被称为 容器。
简单来说,Docker 可以解决这样一个经典问题:
“为什么这个程序在我电脑上能运行,在你电脑上就跑不起来?”
在传统开发模式下,一个项目可能依赖不同版本的语言环境、数据库、系统库和配置。例如:
- Node.js 项目依赖 Node.js 18;
- Java 项目依赖 JDK 17;
- Python 项目依赖 Python 3.11;
- 项目还可能依赖 MySQL、Redis、Nginx 等服务。
如果每个开发者都在自己的电脑上手动安装这些环境,很容易出现版本不一致、配置不统一的问题。而 Docker 的优势就在于:将应用和环境一起打包,保证在不同机器上运行结果尽可能一致。
二、Docker 的核心概念
学习 Docker 之前,需要先理解几个核心概念。
1. 镜像 Image
镜像可以理解为一个“模板”或“安装包”。它包含运行某个应用所需的文件系统、依赖环境、启动命令等内容。
例如:
nginx镜像可以用来启动 Nginx 服务;mysql镜像可以用来启动 MySQL 数据库;node镜像可以用来运行 Node.js 应用;openjdk或eclipse-temurin镜像可以用来运行 Java 应用。
镜像本身是静态的,不能直接提供服务,必须基于镜像创建容器。
2. 容器 Container
容器是镜像运行起来之后的实例。
如果镜像是“类”,那么容器就像是“对象”;如果镜像是“安装包”,那么容器就是“已经安装并运行的软件”。
一个镜像可以创建多个容器,每个容器之间默认相互隔离,拥有自己的文件系统、网络环境和进程空间。
3. Dockerfile
Dockerfile 是一个文本文件,用来描述如何构建镜像。它里面会写明:
- 基于哪个基础镜像;
- 拷贝哪些代码;
- 安装哪些依赖;
- 暴露哪个端口;
- 容器启动时执行什么命令。
通过 Dockerfile,我们可以把项目构建成自定义镜像。
4. 仓库 Repository
Docker 镜像可以存放在镜像仓库中。常见仓库包括:
- Docker Hub;
- 阿里云容器镜像服务;
- 腾讯云容器镜像服务;
- GitHub Container Registry;
- 企业内部私有镜像仓库。
平时执行的 docker pull nginx,就是从远程镜像仓库拉取 nginx 镜像。
三、为什么要使用 Docker?
Docker 并不是为了“炫技”,它解决的是实际工程问题。
1. 环境一致
开发环境、测试环境、生产环境可以使用同一套镜像,减少“环境不同导致的 bug”。
2. 部署简单
传统部署可能需要手动安装运行时、配置环境变量、安装依赖。而 Docker 只需要:
docker run ...
或者:
docker compose up -d
即可启动服务。
3. 隔离性好
不同项目可以运行在不同容器中,互不影响。例如一个项目使用 MySQL 5.7,另一个项目使用 MySQL 8.0,它们可以同时运行在同一台机器上。
4. 方便扩展
Docker 是 Kubernetes、Docker Compose、CI/CD 流水线的重要基础。学会 Docker 后,再学习微服务部署和容器编排会更加容易。
四、Docker 的安装
1. Windows 安装 Docker
Windows 用户推荐安装 Docker Desktop。
下载地址:
https://www.docker.com/products/docker-desktop/
安装完成后,在命令行中执行:
docker version
如果能看到客户端和服务端版本信息,说明安装成功。
2. macOS 安装 Docker
macOS 同样推荐安装 Docker Desktop。安装完成后执行:
docker info
如果能够正常输出 Docker 运行信息,说明 Docker 已经可用。
3. Linux 安装 Docker
以 Ubuntu 为例:
sudo apt update
sudo apt install -y docker.io
sudo systemctl enable docker
sudo systemctl start docker
查看版本:
docker version
如果不想每次都使用 sudo,可以将当前用户加入 docker 用户组:
sudo usermod -aG docker $USER
然后重新登录终端即可。
五、第一个 Docker 命令:运行 Nginx
我们先运行一个最简单的 Nginx 容器。
docker run -d --name my-nginx -p 8080:80 nginx
参数说明:
| 参数 | 含义 |
|---|---|
docker run |
创建并运行容器 |
-d |
后台运行 |
--name my-nginx |
指定容器名称 |
-p 8080:80 |
将宿主机 8080 端口映射到容器 80 端口 |
nginx |
使用 nginx 镜像 |
启动成功后,在浏览器访问:
http://localhost:8080
如果看到 Nginx 欢迎页面,就说明容器运行成功。
查看正在运行的容器:
docker ps
停止容器:
docker stop my-nginx
删除容器:
docker rm my-nginx
删除镜像:
docker rmi nginx
六、常用 Docker 命令速查
1. 镜像相关命令
查看本地镜像:
docker images
拉取镜像:
docker pull nginx
删除镜像:
docker rmi nginx
构建镜像:
docker build -t my-app:1.0 .
2. 容器相关命令
查看运行中的容器:
docker ps
查看所有容器:
docker ps -a
启动容器:
docker start 容器名或容器ID
停止容器:
docker stop 容器名或容器ID
重启容器:
docker restart 容器名或容器ID
删除容器:
docker rm 容器名或容器ID
进入容器:
docker exec -it 容器名 /bin/bash
有些轻量级镜像没有 bash,可以使用:
docker exec -it 容器名 /bin/sh
3. 日志相关命令
查看容器日志:
docker logs 容器名
实时查看日志:
docker logs -f 容器名
查看最近 100 行日志:
docker logs --tail=100 容器名
七、实战项目:使用 Docker 部署一个 Node.js Web 服务
下面我们通过一个简单的 Node.js 项目来演示如何将应用容器化。
项目功能很简单:启动一个 HTTP 服务,访问接口后返回一段 JSON 数据。
1. 项目目录结构
docker-node-demo
├── Dockerfile
├── package.json
└── src
└── app.js
八、源码示例
1. package.json
{
"name": "docker-node-demo",
"version": "1.0.0",
"description": "A simple Node.js demo for Docker beginners",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js"
},
"dependencies": {
"express": "^4.18.2"
}
}
2. src/app.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({
message: 'Hello Docker!',
description: '这是一个运行在 Docker 容器中的 Node.js 服务',
time: new Date().toISOString()
});
});
app.get('/health', (req, res) => {
res.json({
status: 'ok',
uptime: process.uptime()
});
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
3. Dockerfile
# 使用官方 Node.js 镜像作为基础镜像
FROM node:18-alpine
# 设置容器内的工作目录
WORKDIR /app
# 先复制 package.json,便于利用 Docker 缓存
COPY package.json ./
# 安装项目依赖
RUN npm install
# 复制项目源码
COPY src ./src
# 暴露容器端口
EXPOSE 3000
# 容器启动时执行的命令
CMD ["npm", "start"]
九、构建并运行镜像
进入项目根目录:
cd docker-node-demo
构建镜像:
docker build -t docker-node-demo:1.0 .
参数说明:
-t docker-node-demo:1.0:指定镜像名称和标签;.:表示 Dockerfile 所在的构建上下文为当前目录。
构建成功后查看镜像:
docker images
运行容器:
docker run -d --name node-demo -p 3000:3000 docker-node-demo:1.0
访问接口:
curl http://localhost:3000
返回示例:
{
"message": "Hello Docker!",
"description": "这是一个运行在 Docker 容器中的 Node.js 服务",
"time": "2026-01-01T12:00:00.000Z"
}
访问健康检查接口:
curl http://localhost:3000/health
返回示例:
{
"status": "ok",
"uptime": 25.362
}
十、理解 Dockerfile 的构建过程
上面的 Dockerfile 虽然不长,但包含了 Docker 镜像构建的核心逻辑。
FROM node:18-alpine
表示使用 Node.js 18 的 Alpine 版本作为基础镜像。Alpine 是一个非常轻量的 Linux 发行版,适合用来构建体积较小的镜像。
WORKDIR /app
设置容器内部工作目录,后续命令会在 /app 下执行。
COPY package.json ./
RUN npm install
这里先复制 package.json 再安装依赖,是为了利用 Docker 的构建缓存。如果源码发生变化但依赖没有变化,Docker 不需要重新执行 npm install,可以提升构建速度。
COPY src ./src
复制项目源码到镜像中。
EXPOSE 3000
声明容器内部服务监听的端口。需要注意的是,EXPOSE 只是声明,并不会自动把端口映射到宿主机。真正的端口映射需要在 docker run 时使用 -p 参数。
CMD ["npm", "start"]
指定容器启动时默认执行的命令。
十一、使用 .dockerignore 优化构建
在实际项目中,我们通常会添加 .dockerignore 文件,避免把不必要的文件复制进镜像。
例如:
node_modules
npm-debug.log
.git
.gitignore
README.md
.DS_Store
.env
为什么要忽略这些文件?
node_modules应该在容器内安装,避免宿主机依赖和容器环境不一致;.git会增大构建上下文体积;.env可能包含敏感信息,不应该随意打进镜像;- 日志文件、系统文件没有必要进入镜像。
十二、Docker 数据卷 Volume
容器默认是临时的。如果删除容器,容器内部产生的数据也可能随之丢失。对于数据库、上传文件、日志等需要持久化的数据,通常要使用数据卷。
创建数据卷:
docker volume create my-data
查看数据卷:
docker volume ls
运行容器时挂载数据卷:
docker run -d \
--name my-nginx-volume \
-p 8080:80 \
-v my-data:/usr/share/nginx/html \
nginx
其中:
-v my-data:/usr/share/nginx/html
表示把 Docker 数据卷 my-data 挂载到容器内的 /usr/share/nginx/html 目录。
除了命名数据卷,也可以使用宿主机目录挂载:
docker run -d \
--name nginx-local \
-p 8080:80 \
-v $(pwd)/html:/usr/share/nginx/html \
nginx
这样可以很方便地把本地静态页面交给 Nginx 容器运行。
十三、Docker 网络基础
容器之间默认是隔离的,但 Docker 提供了网络机制,让容器之间可以通信。
创建网络:
docker network create app-net
运行 MySQL 容器:
docker run -d \
--name mysql-demo \
--network app-net \
-e MYSQL_ROOT_PASSWORD=123456 \
-e MYSQL_DATABASE=testdb \
mysql:8.0
运行应用容器:
docker run -d \
--name app-demo \
--network app-net \
-p 3000:3000 \
docker-node-demo:1.0
在同一个 Docker 网络中,容器可以通过容器名称访问彼此。例如应用连接 MySQL 时,数据库主机名可以写成:
mysql-demo
而不是 localhost。
这是新手非常容易踩坑的地方:在容器内部,localhost 指的是容器自己,而不是宿主机,也不是其他容器。
十四、使用 Docker Compose 管理多个服务
当项目只有一个容器时,使用 docker run 还比较方便。但如果项目包含应用、数据库、Redis、Nginx 等多个服务,手写多个 docker run 命令就会变得复杂。此时可以使用 Docker Compose。
Docker Compose 使用 docker-compose.yml 或 compose.yml 文件描述多个容器服务。
下面是一个示例:
services:
app:
build: .
container_name: node-demo-compose
ports:
- "3000:3000"
environment:
- PORT=3000
networks:
- app-net
nginx:
image: nginx:latest
container_name: nginx-demo-compose
ports:
- "8080:80"
networks:
- app-net
networks:
app-net:
driver: bridge
启动服务:
docker compose up -d
查看服务:
docker compose ps
查看日志:
docker compose logs -f
停止并删除服务:
docker compose down
Docker Compose 的好处是可以把服务配置写进文件中,方便版本管理,也方便团队成员一键启动相同环境。
十五、镜像标签与版本管理
构建镜像时,建议不要总是使用 latest 标签。因为 latest 并不代表最新稳定版本,它只是一个普通标签。
推荐方式:
docker build -t docker-node-demo:1.0.0 .
docker build -t docker-node-demo:2026-01-01 .
docker build -t docker-node-demo:dev .
在生产环境中,最好使用明确版本号,例如:
docker-node-demo:1.0.0
这样在出现问题时,可以快速回滚到旧版本。
十六、Docker 新手常见问题
1. 为什么容器启动后马上退出?
常见原因是容器主进程执行完了。例如你运行:
docker run ubuntu
Ubuntu 容器会立即退出,因为没有持续运行的前台进程。
可以这样进入交互模式:
docker run -it ubuntu /bin/bash
2. 为什么访问不到容器服务?
可能原因包括:
- 容器内部服务没有监听正确端口;
- 没有使用
-p映射端口; - 应用只监听了
127.0.0.1,没有监听0.0.0.0; - 防火墙或安全组未放行端口。
对于 Web 服务,建议监听:
app.listen(3000, '0.0.0.0');
3. 容器里改了文件,为什么删除容器后没了?
容器文件系统默认不是用来长期保存数据的。需要持久化的数据应该使用:
- Docker Volume;
- 宿主机目录挂载;
- 外部数据库或对象存储。
4. Docker 镜像太大怎么办?
可以从几个方面优化:
- 使用更小的基础镜像,例如
alpine; - 使用
.dockerignore; - 减少不必要的软件安装;
- 合并或优化构建层;
- 对编译型项目使用多阶段构建。
十七、进阶示例:多阶段构建
对于前端、Go、Java 等项目,可以使用多阶段构建减少最终镜像体积。下面以一个简单前端项目为例:
# 第一阶段:构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
RUN npm run build
# 第二阶段:运行阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
多阶段构建的核心思想是:
- 第一阶段负责编译、打包;
- 第二阶段只保留运行时真正需要的文件;
- 最终镜像更小、更安全、更适合生产环境。
十八、推荐的 Docker 学习路线
对于新手来说,可以按照以下顺序学习:
- 理解镜像、容器、Dockerfile;
- 掌握常用 Docker 命令;
- 学会构建自己的应用镜像;
- 学会端口映射、数据卷、网络;
- 学会 Docker Compose;
- 学习镜像优化和多阶段构建;
- 了解 CI/CD 中如何构建和推送镜像;
- 再进一步学习 Kubernetes。
不要一开始就直接学习 Kubernetes。Kubernetes 是建立在容器基础之上的编排平台,如果 Docker 基础不牢,学习 Kubernetes 会非常吃力。
十九、完整源码汇总
下面再次汇总本文示例项目源码。
package.json
{
"name": "docker-node-demo",
"version": "1.0.0",
"description": "A simple Node.js demo for Docker beginners",
"main": "src/app.js",
"scripts": {
"start": "node src/app.js"
},
"dependencies": {
"express": "^4.18.2"
}
}
src/app.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({
message: 'Hello Docker!',
description: '这是一个运行在 Docker 容器中的 Node.js 服务',
time: new Date().toISOString()
});
});
app.get('/health', (req, res) => {
res.json({
status: 'ok',
uptime: process.uptime()
});
});
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server is running on port ${PORT}`);
});
Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY src ./src
EXPOSE 3000
CMD ["npm", "start"]
.dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.DS_Store
.env
compose.yml
services:
app:
build: .
container_name: docker-node-demo
ports:
- "3000:3000"
environment:
- PORT=3000
restart: unless-stopped
启动命令:
docker compose up -d
停止命令:
docker compose down
二十、总结
Docker 的核心价值在于:让应用和运行环境一起交付。对于新手来说,刚开始不需要掌握所有复杂概念,只要先理解镜像、容器、Dockerfile、端口映射、数据卷和 Docker Compose,就已经可以完成大多数基础开发和部署工作。
本文通过 Nginx 示例和 Node.js 实战项目,演示了 Docker 的基本使用流程:
- 拉取并运行镜像;
- 编写 Dockerfile;
- 构建自定义镜像;
- 运行容器并映射端口;
- 使用
.dockerignore优化构建; - 使用 Volume 持久化数据;
- 使用 Docker Compose 管理服务。
如果你是 Docker 新手,建议不要只看概念,一定要亲自敲一遍命令,构建一个自己的镜像,并成功运行起来。只有当你真正把项目装进容器,访问到接口,查看到日志,处理过端口和数据卷问题之后,Docker 的知识才会从“看懂”变成“会用”。
Docker 是通往 DevOps、微服务和云原生的重要入口。掌握它,会让你的开发和部署能力迈上一个新的台阶。