Docker 核心进阶指南:卷、网络、端口与日志,一篇讲透
2026/6/15 14:06:50 网站建设 项目流程

很多 Docker 初学者会问:容器删了,数据去哪了?两个容器怎么通信?怎么让外部访问容器里的服务?日志把磁盘撑爆了怎么办?
本文将从零开始,系统讲解 Docker 的卷(Volume)、网络(Network)、端口映射(Port)和日志(Logging),配合大量实战命令和场景示例,帮你彻底搞懂这些核心概念。


一、为什么需要卷?—— 容器是“一次性”的

默认情况下,容器里创建的文件(比如数据库的数据文件、应用生成的日志)都存储在容器的可写层。一旦容器被删除,这些数据就会永久消失

bash

# 示例:运行一个临时容器,创建一个文件 docker run --name temp alpine touch /data/hello.txt # 删除容器 docker rm temp # 文件已经丢失,无法找回

为了解决“数据持久化”和“容器间共享数据”的问题,Docker 提供了三种存储方式:

类型特点适用场景
Volume由 Docker 管理,存放在/var/lib/docker/volumes/,支持多种驱动生产环境持久化数据,最推荐
Bind mount将宿主机任意路径挂载到容器,权限依赖宿主机目录结构开发环境热加载代码、配置文件
tmpfs仅存于宿主机内存中,容器停止后数据消失存放敏感或临时数据,如密钥

1.1 Volume — 最推荐的持久化方式

基本操作

bash

# 创建卷 docker volume create mydata # 列出所有卷 docker volume ls # 查看卷详情 docker volume inspect mydata # 使用卷运行容器(挂载到容器的 /var/lib/mysql) docker run -d --name mysql-db -v mydata:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0 # 删除卷(需要先停止并删除使用该卷的容器) docker volume rm mydata
实战:MySQL 数据持久化

bash

# 1. 创建数据卷 docker volume create mysql-data # 2. 运行 MySQL 容器,挂载卷 docker run -d \ --name mysql-prod \ -v mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=root123 \ -p 3306:3306 \ mysql:8.0 # 3. 连接 MySQL 并创建测试数据库 docker exec -it mysql-prod mysql -uroot -proot123 -e "CREATE DATABASE testdb;" # 4. 停止并删除容器(卷仍然存在) docker stop mysql-prod && docker rm mysql-prod # 5. 新建一个容器,使用同一个卷,数据依然存在 docker run -d --name mysql-new -v mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root123 mysql:8.0 docker exec -it mysql-new mysql -uroot -proot123 -e "SHOW DATABASES;" # testdb 还在

1.2 Bind Mount — 开发热加载利器

直接将宿主机的文件夹映射到容器,修改宿主机代码,容器内立即生效(常用于开发环境)。

bash

# 将当前目录的 ./app 挂载到容器的 /app docker run -d \ --name dev-node \ -v "$(pwd)"/app:/app \ -w /app \ node:18 npm run dev

注意:Bind mount 依赖宿主机绝对路径,权限问题较多(如容器内用户 uid=1000,而宿主机文件属于 root)。

1.3 tmpfs — 内存级临时存储

bash

# 挂载 tmpfs 到容器的 /tmp,数据不落地磁盘 docker run -d --name temp-cache --tmpfs /tmp:rw,noexec,nosuid,size=2g alpine tail -f /dev/null

二、Docker 网络 — 让容器互相通信

Docker 默认提供三种网络驱动:bridge(默认)、hostnone。此外还有overlay(Swarm/K8s)、macvlan等。

2.1 默认 bridge 网络

所有未指定网络的容器都会自动连接到名为bridge的默认网络。但默认 bridge 不支持容器名自动 DNS 解析,只能通过 IP 互相访问,且容器间需要--link才能通信(已过时)。

bash

# 查看默认 bridge 网络 docker network inspect bridge # 运行两个容器 docker run -d --name c1 alpine sleep 3600 docker run -d --name c2 alpine sleep 3600 # 获取 c1 的 IP docker inspect c1 | grep IPAddress # 在 c2 中 ping c1 的 IP(可以,但无法用 ping c1 域名) docker exec c2 ping -c 2 172.17.0.2

2.2 用户自定义 bridge — 推荐方式

自定义 bridge 网络具备自动 DNS 解析功能,容器之间可以通过容器名直接通信。

bash

# 创建自定义网络 docker network create mynet # 运行两个容器并连接到 mynet docker run -d --name web --network mynet nginx docker run -d --name app --network mynet alpine sleep 3600 # 在 app 容器中 ping web(通过容器名) docker exec app ping web # 成功解析

实战:一个 WordPress + MySQL 的例子

bash

# 创建公共网络 docker network create wp-net # MySQL 容器 docker run -d --name mysql-db \ --network wp-net \ -e MYSQL_ROOT_PASSWORD=root \ -e MYSQL_DATABASE=wordpress \ mysql:8.0 # WordPress 容器 docker run -d --name wordpress \ --network wp-net \ -e WORDPRESS_DB_HOST=mysql-db \ -e WORDPRESS_DB_USER=root \ -e WORDPRESS_DB_PASSWORD=root \ -p 8080:80 \ wordpress:latest

现在通过http://localhost:8080即可访问 WordPress,两个容器通过mysql-db这个容器名自动通信。

2.3 host 网络 — 与宿主机共享网络栈

使用--network host后,容器不再拥有独立 IP,而是直接使用宿主机的网络栈(端口也直接暴露在宿主机上)。

bash

# 运行 nginx 使用 host 网络,访问 http://localhost 即可看到 nginx 首页 docker run -d --name nginx-host --network host nginx

适用场景:追求极致网络性能、不需要端口映射(如某些监控 agent)。

2.4 none 网络 — 完全隔离

容器只有 loopback 接口,无法访问外部网络。

bash

docker run -d --name isolated --network none alpine sleep 3600

三、端口映射 — 让外部访问容器服务

容器默认运行在隔离网络内,外部无法直接访问。通过-p-P将宿主机端口映射到容器端口。

3.1 基本格式

bash

# 宿主机端口:容器端口 -p 8080:80 # 指定 IP(默认 0.0.0.0) -p 127.0.0.1:8080:80 # 只绑定容器端口(随机映射宿主机端口) -p 80 # UDP 端口 -p 53:53/udp # 多个端口映射 -p 8080:80 -p 8443:443

3.2 实战:运行一个 Flask 应用

python

# app.py from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return "Hello from Docker!" if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)

bash

# 构建镜像并运行,映射宿主机 5001 -> 容器 5000 docker build -t flask-app . docker run -d --name myflask -p 5001:5000 flask-app

访问http://localhost:5001即可看到输出。

3.3 查看端口映射

bash

docker port myflask # 输出 5000/tcp -> 0.0.0.0:5001 docker ps # PORTS 列显示映射关系

3.4 随机端口分配-P

-P会将 Dockerfile 中EXPOSE声明的端口自动映射到宿主机随机高位端口。

bash

docker run -d --name random-nginx -P nginx docker port random-nginx # 显示 80/tcp -> 0.0.0.0:32768

四、Docker 日志 — 控制、查看与管理

容器产生的标准输出(stdout/stderr)会被 Docker 捕获并交给日志驱动处理。默认驱动是json-file,将日志以 JSON 格式保存在宿主机/var/lib/docker/containers/<container_id>/<container_id>-json.log

4.1 查看日志 —docker logs

bash

# 查看全部日志 docker logs <container> # 实时跟踪(类似 tail -f) docker logs -f <container> # 查看最后 100 行 docker logs --tail 100 <container> # 带时间戳 docker logs -t <container> # 查看最近 5 分钟的日志 docker logs --since 5m <container>

4.2 限制日志大小 — 防止磁盘爆满

如果不加限制,长期运行的容器(如 MySQL、Nginx)会产生 GB 级别的日志,占满磁盘。通过--log-opt限制:

bash

# 单个日志文件最大 10M,最多保留 3 个文件 docker run -d --name app \ --log-opt max-size=10m \ --log-opt max-file=3 \ nginx

也可以在/etc/docker/daemon.json中全局配置:

json

{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }

然后重启 Docker:systemctl restart docker(会重启所有容器,谨慎操作)。

4.3 切换日志驱动

Docker 支持多种日志驱动,常用的有:

驱动说明适用场景
json-file默认,写入本地 JSON 文件单机开发、测试
journald发送到 systemd journal使用 systemd 的 Linux 发行版
syslog发送到 syslog 服务传统日志集中化
fluentd发送到 Fluentd 日志收集器容器化微服务日志聚合
gelf发送到 Graylog 或 ELK生产日志分析
none禁用日志(不建议)-

示例:使用 syslog 驱动

bash

docker run -d --name syslog-app --log-driver syslog --log-opt syslog-address=tcp://192.168.1.100:514 alpine sh -c "while true; do echo hello; sleep 1; done"

4.4 生产环境日志最佳实践

  1. 始终限制日志大小max-size/max-file

  2. 不要在容器内写日志文件,而是输出到 stdout/stderr(Docker 自动收集)

  3. 使用集中式日志系统(如 ELK、Loki、Graylog),配合fluentdgelf驱动

  4. 定期清理旧日志docker system prune -a --volumes或手动删除/var/lib/docker/containers/*/*.log


五、综合实战:部署一个完整的 Web 应用

我们将部署一个Node.js + Redis的计数器应用,包含卷(Redis 数据持久化)、自定义网络、端口映射、日志限制。

5.1 目录结构

text

myapp/ ├── Dockerfile ├── index.js └── package.json

index.js

javascript

const express = require('express'); const redis = require('redis'); const app = express(); const client = redis.createClient({ host: 'redis-server', port: 6379 }); client.set('visits', 0); app.get('/', async (req, res) => { const visits = await client.get('visits'); res.send(`Number of visits: ${visits}`); await client.set('visits', parseInt(visits) + 1); }); app.listen(3000, () => console.log('App listening on port 3000'));

package.json

json

{ "name": "myapp", "scripts": { "start": "node index.js" }, "dependencies": { "express": "^4.18.2", "redis": "^4.6.5" } }

Dockerfile

dockerfile

FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . CMD ["npm", "start"]

5.2 构建并运行

bash

# 1. 创建自定义网络 docker network create app-net # 2. 创建 Redis 数据卷 docker volume create redis-data # 3. 运行 Redis 容器(挂载卷,限制日志) docker run -d --name redis-server \ --network app-net \ -v redis-data:/data \ --log-opt max-size=5m --log-opt max-file=2 \ redis:7-alpine # 4. 构建 Node 镜像 docker build -t myapp . # 5. 运行 Node 应用(端口映射,日志限制) docker run -d --name myapp \ --network app-net \ -p 3000:3000 \ --log-opt max-size=5m --log-opt max-file=2 \ myapp

访问http://localhost:3000,每次刷新计数器加 1,Redis 数据持久化在卷中,即使删除 redis-server 容器再重新创建(使用同一卷),计数依然保留。


六、常见问题与 FAQ

Q1:为什么我的 bind mount 在容器里看不到文件?
A:检查宿主机路径是否存在,以及容器内挂载点是否覆盖了原有目录内容(bind mount 会使目标目录原有内容隐藏)。

Q2:容器之间用--link和自定义网络有什么区别?
A:--link是旧版解决方案,只允许单向通信且已过时;自定义网络提供自动 DNS,容器名即可通信,推荐使用。

Q3:docker logs显示日志,但为什么我的日志驱动是journald还能看?
A:docker logs实际上会从日志驱动读取,如果驱动不支持(如syslog),docker logs会报错。json-filejournald都支持docker logs

Q4:如何清理所有未使用的卷?

bash

docker volume prune

Q5:容器端口映射后,宿主机端口被占用怎么办?
A:换一个宿主机端口,或停止占用端口的进程,或使用随机端口-P让 Docker 自动分配。


总结

概念核心要点
数据持久化首选 Volume;开发热加载用 bind mount;临时敏感用 tmpfs
网络自定义 bridge 网络实现容器名自动解析;host 高性能;none 隔离
端口-p 宿主机:容器对外暴露服务;-P随机映射 EXPOSE 端口
日志默认 json-file 需限制 max-size;生产环境建议集中式日志收集

掌握这些内容,你已经可以熟练使用 Docker 部署有状态应用、搭建微服务通信、并合理管理日志了。如果你觉得本文对你有帮助,欢迎点赞、收藏、转发~

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询