因为这是一个笔记,不是教程,所以内容记录的比较散,不具备教程逻辑。我只记录我认为的重点。
首先基于我这个笔记的开源项目地址:express-demo
里面基于 express.js
框架搭建的一个标准的 node.js web 项目模板。也是为了复习 node.js 搭建学习使用的。
[!NOTE]
博客服务名 web 和 db 跟实际开源的名称不一致。意思一致
一、Docker基础知识
1 | # docker-compose.yml |
Docker 各组件关系图解
1 | [源码] → Dockerfile → docker build → [镜像] → docker run → [容器] |
这里其实我想搞懂的就是docker-compose.yml启动流程,还有构建细节 build: .
。build: .
表示使用当前项目源码中的 Dockerfile 构建镜像。因为没有镜像就没有办法构建容器,而docker-compose 核心功能就是进行容器编排,因此假如说本地手动有现成的基于Dockerfile 构建的镜像,那其实docker-compose.yml 也可以直接使用,类似数据库服务中的image: mysql:8.0
- 必要性:若没有 Dockerfile,Compose 无法自动生成镜像(除非直接使用现有镜像如
mysql:8.0
)。
2. 手动构建镜像后的 Compose 使用
假设你已手动构建镜像:
1 | # 手动构建镜像(假设镜像名为 my-api-image:v1) |
修改 docker-compose.yml
:
1 | services: |
- 结果:
docker-compose up -d
会直接使用my-api-image:v1
启动容器,无需重新构建。 - 通信能力:只要
api
和mysql
在同一网络(blog-network
),通信完全正常。
如何理解Dockerfile 和镜像的关系
Dockerfile 是一个文本文件,包含了一系列用于构建 Docker 镜像的指令(如 FROM
、RUN
、COPY
等)。
镜像 是 Dockerfile 通过 docker build
命令生成的静态文件,它是一个分层的只读模板,用于创建容器。
核心关系:
- Dockerfile → 镜像
- Dockerfile 是“菜谱”,镜像是由它“烹饪”出的“成品”。
- 每条 Dockerfile 指令会在镜像中生成一个只读层(Layer),最终多层叠加形成完整的镜像。
- 镜像 → 容器
- 镜像是静态模板,容器是镜像的动态运行实例。
1 | [宿主机] |
1. Docker启动项目命令区别
行为 | docker-compose up -d --build |
docker-compose build + docker-compose up -d |
---|---|---|
镜像构建时机 | 每次运行都强制重新构建 | 仅 build 时构建,up 直接使用现有镜像 |
是否自动启动服务 | 是(构建后立即启动) | 否(需手动运行 up ) |
缓存利用 | 总是重新构建,可能忽略缓存 | 可手动控制是否清理缓存(如 --no-cache ) |
适用场景 | 开发环境(代码频繁变更) | 生产环境(构建和部署分离) |
关闭占用端口的进程:
1 | # Linux/Mac |
2. Dockerfile关键点
- 构建依赖时要安装所有依赖而非仅生产依赖,因为项目是加入了 ts 类型,ts 只在开发环境使用
1 | # ❌ 错误方式:导致tsc命令不可用 |
3. 如何去理解Docker 挂载机制
Docker 挂载机制的核心:双向数据同步
核心概念:挂载是共享而非复制
- 不是数据拷贝:挂载操作不会将文件复制到容器内,而是直接暴露宿主机的存储位置。
- 没有主从之分:宿主机和容器是平等的存储访问者,任何一方的修改都会影响另一方。
Docker 的挂载分为两类,但数据同步逻辑相同:
卷挂载(Volume)
- Docker 管理的存储位置(通常位于
/var/lib/docker/volumes/
) - 示例:MySQL 数据持久化
- Docker 管理的存储位置(通常位于
绑定挂载(Bind Mount)
- 直接挂载宿主机的文件或目录
- 示例:Nginx 配置或静态资源挂载
无论是哪种挂载方式,Docker 都会通过 Linux 内核的 联合文件系统(UnionFS) 实现以下机制:
- 挂载本质:将宿主机文件/目录的 物理存储地址 映射到容器内。
- 数据同步:对挂载目录的读写操作,实际上是直接操作宿主机或卷的物理文件系统。
- 实时性:修改会立即生效(无需重启容器),因为双方共享同一块磁盘区域。
二、Docker 网络与通信知识体系梳理
1.服务名称(Service Name)
在 docker-compose.yml
中定义的 services
名称(如 web
、db
)。
1 | services: |
- 容器内可通过
http://web:3000
或mysql://db:3306
访问其他服务。
2.网络名称(Network Name)
在 docker-compose.yml
中自定义的网络(如 blog-network
)
1 | networks: |
3.服务名称与网络名称的区别
- 网络名称 (
blog-network
):- 是通信基础设施,相当于一个虚拟局域网
- 连接到同一网络的容器可以相互通信
- 是容器间通信的媒介
- 服务名称 (
db
):- 是容器在网络内的”主机名”(hostname)
- 作为容器的网络标识符
- 由Docker的内部DNS服务自动注册
连接过程解析:
当你设置DB_HOST=db
时,这里的流程是:
- web容器需要连接数据库
- web查找主机名为
mysql
的服务 - Docker网络的DNS服务将
db
解析为MySQL容器的IP地址,因为 db 跟 web 属于同一blog-network网络下 - 连接建立
这就好比:
blog-network
= 你家的WiFi网络mysql
= 连接到该WiFi的一台设备的主机名
4.容器间通信
1. 同一网络内的通信
通过服务名访问:Docker 内置 DNS 自动解析服务名为容器 IP。
1
2
3# 在 web 容器中访问 db 服务
ping db
curl http://db:3306通过容器名访问(不推荐):需显式指定
container_name
。1
2
3services:
db:
container_name: mysql-db1
2# 在 web 容器中访问
ping mysql-db
2. 不同网络间的通信
方案 1:将容器加入多个网络。
1
2
3
4
5services:
web:
networks:
- frontend-network
- backend-network方案 2:通过网关 IP 访问(需知道目标网络网关地址)。
三、Docker存储与数据管理
1. 卷命名规则
- 格式:
项目名称_卷名称
- 示例:
my-blog-api_mysql-data
- 好处:避免不同项目间卷名冲突,自动组织和分类
2. 数据持久化
- 数据库数据:
mysql-data:/var/lib/mysql
- 日志文件:
./logs:/app/logs
四、进入容器-调试与日志管理
1. 容器管理命令
1 | # 进入API(node.js 项目)容器 |
2. 日志查看方法
1 | # 查看所有服务日志 |
3. 宿主机直接查看日志
因为在docker-compose.yml
中做了挂载,所以可以在宿主中直接查看
1 | cat ./logs/combined.log |
五、常见问题与解决方案
1. 构建失败
- 问题:
tsc: not found
- TypeScript编译失败 - 原因:Dockerfile中只安装了生产依赖
- 解决方案:修改为安装所有依赖
pnpm install
2. 数据库连接问题
- 问题:
connect ECONNREFUSED 192.168.148.2:3306
- 解决方案:
- 检查MySQL容器是否正常启动
- 确认环境变量配置正确
- 添加MySQL健康检查,确保API服务在MySQL就绪后启动
3. 授权错误
- 问题:
Access denied for user 'root'@'192.168.148.3'
- 解决方案:
- 检查
.env
文件中的数据库凭据 - 修复MySQL环境变量中的额外等号:
MYSQL_ROOT_PASSWORD==${DB_PASSWORD}
- 检查
六、最佳实践总结
- 网络配置:使用自定义网络,通过服务名互相访问
- 数据持久化:使用命名卷保存重要数据
- 环境变量:使用
.env
文件集中管理敏感配置 - 构建优化:构建时安装全部依赖,运行时精简
- 日志管理:将日志挂载到宿主机便于查看和分析