Docker文件操作、数据卷、挂载
一:容器文件操作
在Docker环境中,管理容器内部的文件是一个常见的需求。
无论是为了配置应用、备份数据还是调试问题,了解如何高效地进行文件操作都是非常重要的。
docker cp
命令提供了一种简单的方法来在宿主主机和容器之间复制文件或目录。
快速文件传输:
-
当我们需要快速地在宿主主机和容器之间传输文件,而不想通过网络或构建新的镜像时,
docker cp
提供了一种简单直接的方法。
临时性文件操作:
-
对于临时性的文件修改或者调试,比如临时修改配置文件、测试新功能等,
docker cp
可以让我们迅速地将文件传入传出容器,无需重启容器或重新构建镜像。
其基本语法如下:
docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH
docker cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH
-
CONTAINER
: 目标容器的名称或ID。 -
SRC_PATH
: 源路径,可以是宿主主机上的路径或是容器内的路径。 -
DEST_PATH
: 目标路径,同样可以是宿主主机上的路径或是容器内的路径。
1、Nginx案例
启动Nginx容器
首先,我们需要启动一个Nginx容器:
docker run --name mynginx -d nginx
从宿主主机复制文件到Nginx容器
假设我们有一个名为index.html
的静态网页文件想要放到Nginx的默认网页目录中(通常是/usr/share/nginx/html
)
我们可以使用以下命令:
docker cp index.html mynginx:/usr/share/nginx/html/
从Nginx容器复制文件到宿主主机
如果我们想从容器内获取某个文件(例如修改后的index.html
),我们可以这样做:
docker cp mynginx:/usr/share/nginx/html/index.html .
这将把容器内的index.html
文件复制到当前工作目录下。
2、MySQL案例
准备一个 .sql
文件
-- init.sql 示例内容
CREATE DATABASE IF NOT EXISTS testdb;
USE testdb;CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(100)
);INSERT INTO users (name) VALUES ('Alice'), ('Bob');
启动MySQL容器
使用官方的MySQL镜像启动一个新的容器,并设置root用户的密码:
docker run --name my-mysql \-e MYSQL_ROOT_PASSWORD=123456 \-p 13306:3306 \-d mysql:8.0.22 \--default-authentication-plugin=mysql_native_password \--character-set-server=utf8mb4 \--collation-server=utf8mb4_unicode_ci
参数 | 含义 |
---|---|
--name my-mysql | 给容器起个名字叫 my-mysql |
-e MYSQL_ROOT_PASSWORD=123456 | 设置MySQL的root用户密码为 123456 |
-p 13306:3306 | 将宿主机的 13306 端口映射到容器的 3306 端口(避免与本机MySQL冲突) |
-d mysql:8.0.22 | 后台运行名为 mysql:8.0.22 的镜像 |
--default-authentication-plugin=mysql_native_password | 使用兼容性更好的认证插件 |
--character-set-server=utf8mb4 | 设置默认字符集为 utf8mb4 支持中文和表情符号 |
--collation-server=utf8mb4_unicode_ci | 设置排序规则 |
将本地 SQL 文件复制到容器中
# 假设我们的 init.sql 在 /root/mysql_demo 目录下
docker cp /root/mysql_demo/init.sql my-mysql:/tmp/init.sql
在容器中执行 SQL 文件导入数据
docker exec -it my-mysql mysql -u root -p123456 -e "source /tmp/init.sql"
导出数据库为新的 SQL 文件
docker exec my-mysql mysqldump -u root -p123456 --all-databases > /root/mysql_demo/backup_full.sql
docker exec my-mysql mysqldump -u root -p123456 testdb > /root/mysql_demo/backup_testdb.sql
也可以先在容器里生成备份再拷贝出来
# 在容器内生成备份
docker exec my-mysql sh -c 'mysqldump -u root -p"123456" testdb > /tmp/testdb_backup.sql'
# 把备份文件复制到宿主机
docker cp my-mysql:/tmp/testdb_backup.sql /root/mysql_demo/testdb_backup.sql
docker cp
适用于小规模的文件传输。如果涉及到大量数据的导入导出,这种方法可能不够高效,且容易出错。例如,在处理数据库备份时,虽然可以先通过命令行工具生成备份文件再用
docker cp
拷贝出来,但对于非常大的数据库,这并不是最理想的选择。
如果依赖
docker cp
来进行所有文件交换,那么随着容器的销毁,这些手动拷贝的数据也会丢失。因此,对于需要长期存储的数据,应该考虑使用数据卷(data volumes)或绑定挂载(bind mounts),而不是仅依赖
docker cp
。
3、容器持久化存储
写时复制机制
Docker镜像由多个只读层叠加而成,启动容器时,Docker会加载只读镜像层并在镜像栈顶部添加一个读写层。
如果运行中的容器修改了现有的一个已经存在的文件,那该文件将会从读写层下面的只读层复制到读写层,该文件的只读版本依然存在,只是已经被读写层中该文件的副本所隐藏,此即"写时复制(COW)"机制。
我们知道,容器在创建之后,实际上我们在容器中创建和修改的文件,实际上是被容器的分层机制保存在最顶层的容器层进行操作的,为了保护下面每一层的镜像不被修改,所以才有了这样的CopyOnWrite特性。
原因 | 结果 |
---|---|
Docker 使用 Copy-on-Write 机制 | 文件修改只发生在容器层,不影响镜像层 |
容器层与容器生命周期绑定 | 容器销毁时,容器层也被删除 |
但是这样也会导致容器在销毁时数据的丢失,当我们销毁容器重新创建一个新的容器时,所有的数据全部丢失,直接回到梦开始的地方。
在某些情况下,我们可能希望对容器内的某些文件进行持久化存储,就需要使用到docker中提供给我们的持久化存储几种方式。
存储方式 | 特点 | 是否推荐 |
---|---|---|
数据卷(Data Volume) | 由 Docker 管理,独立于容器生命周期 | ✅ 推荐 |
数据卷容器(Data Volume Container) | 使用一个专门容器提供数据卷供其他容器挂载 | ❌ 旧版用法,现已被简化 |
目录挂载(绑定挂载) | 将宿主机目录直接挂载到容器中 | ✅ 推荐 |
tmpfs 挂载 | 仅存在于内存中,重启后丢失 | ❌ 不适合持久化 |
二:数据卷(Named Volume)
数据卷是 Docker 提供的一种持久化存储机制,它独立于容器的生命周期存在。
这意味着即使删除了容器,只要不删除数据卷,其中的数据依然可以被新创建的容器使用。
数据卷的特点
-
独立性:数据卷与容器分离,不受容器生命周期影响。
-
共享性:支持在多个容器之间共享数据。
-
高效性:绕过联合文件系统(UnionFS),提供更快的读写速度。
-
管理便捷:通过 Docker 命令直接管理,简化操作流程。
数据卷的位置
默认情况下,Docker 将数据卷存储在主机的 /var/lib/docker/volumes/
目录下。
每个数据卷都有一个唯一的名称和对应的目录结构。
使用场景:
-
存储数据库数据(如 MySQL、PostgreSQL)
-
存放日志文件
-
开发环境中映射源码目录(虽然更推荐 bind mount)
-
在多个服务之间共享配置文件或缓存
1、数据卷操作命令
创建数据卷
docker volume create my_volume
这会创建一个名为 my_volume
的数据卷。
查看已有数据卷
docker volume ls
输出示例:
DRIVER VOLUME NAME
local my_volume
local mysql_data
查看数据卷详细信息
[root@localhost volumes]# docker volume inspect my_volume
[{"CreatedAt": "2025-05-30T15:57:40+08:00","Driver": "local","Labels": null,"Mountpoint": "/var/lib/docker/volumes/my_volume/_data","Name": "my_volume","Options": null,"Scope": "local"}
]
可以看到数据卷在宿主机上的实际路径(一般为 /var/lib/docker/volumes/my_volume/_data
)。
删除数据卷
docker volume rm my_volume
注意:如果还有容器在使用这个数据卷,必须先停止并删除这些容器才能删除数据卷。
2、使用方式
在运行容器时挂载数据卷
格式:
docker run -v <volume_name>:<container_path> ...
例如:
docker run -d \--name n1 \-v v1:/usr/share/nginx/html \-p 80:80 \nginx
这条命令表示:
创建一个名为 nginx_html
的数据卷
如果指定的 volume 不存在:
Docker 会自动为我们 创建一个新的命名数据卷(Named Volume)
名字就是我们写的那个
nginx_html
然后把这个新创建的数据卷挂载到容器中的指定路
将其挂载到容器的 /usr/share/nginx/html
路径下
我挂载了一个空的数据卷(volume)到容器的某个目录(如
/usr/share/nginx/html
),请问容器中的/usr/share/nginx/html
是否会被清空?我运行了 docker run -v my_volume:/path/in/container ...
my_volume
是一个 新创建的空数据卷容器镜像在
/path/in/container
这个位置原本有文件那么 Docker 会自动把镜像中原路径下的内容复制到数据卷中,然后才挂载这个数据卷进容器。
这是 Docker 的一种保护机制,目的是:
防止用户误操作导致服务启动失败。
如果直接挂载一个空卷,而容器期望某些配置或静态资源存在,会导致容器启动后服务不可用。
所以 Docker 在第一次挂载空卷时,会自动将镜像中原有的内容“迁移”过去,确保服务能正常运行。
如果我们不希望容器原有的内容被复制进来,可以这样做:
方法一:手动初始化数据卷
提前准备好我们自己的内容放入数据卷,这样就不会触发自动复制。
方法二:使用绑定挂载(Bind Mount)
如果我们希望完全控制内容,可以使用绑定挂载代替命名卷
挂载多个数据卷
docker run -d \--name mynginx \-p 80:80 \-v nginx_html:/usr/share/nginx/html \-v nginx_conf:/etc/nginx/conf.d \-v nginx_logs:/var/log/nginx \nginx
3、Nginx + 数据卷
步骤 1:创建并启动 Nginx 容器
docker run -d \--name mynginx \-v nginx_html:/usr/share/nginx/html \-p 80:80 \nginx
步骤 2:向数据卷中添加网页内容
# 创建测试文件
echo "Hello from Data Volume" > index.html# 将文件复制进容器(自动写入数据卷)
docker cp index.html mynginx:/usr/share/nginx/html/
访问浏览器:http://192.168.66.146/
,应能看到页面显示“Hello from Data Volume”。
步骤 3:删除容器并重建
docker stop mynginx && docker rm mynginxdocker run -d \--name mynginx_new \-v nginx_html:/usr/share/nginx/html \-p 80:80 \nginx
4、MySQL + 数据卷
步骤 1:创建并启动 MySQL 容器
docker run -d \--name mysql_db \-v mysql_data:/var/lib/mysql \-e MYSQL_ROOT_PASSWORD=123456 \mysql:8.0.22
步骤 2:初始化数据库
docker exec -it mysql_db bashmysql -uroot -p123456CREATE DATABASE testdb;
USE testdb;
CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100));
INSERT INTO users (name) VALUES ('Alice'), ('Bob');
步骤 3:删除容器并重建
docker stop mysql_db && docker rm mysql_dbdocker run -d \--name mysql_db_new \-v mysql_data:/var/lib/mysql \-e MYSQL_ROOT_PASSWORD=123456 \mysql:8.0.22
进入新容器查询数据:
方式 | 类比 |
---|---|
docker exec -it xxx bash | 打开电脑 → 登录系统 → 手动输入命令 |
docker exec -it xxx 命令 | 直接远程执行某个程序,不需要登录系统 |
docker exec -it mysql_db_new mysql -uroot -p123456 -e "SELECT * FROM testdb.users;"
我们应该看到之前插入的数据:“Alice” 和 “Bob” —— 数据依然存在!
5、总结
操作 | 命令 | 说明 |
---|---|---|
创建数据卷 | docker volume create myvol | 创建一个名为 myvol 的数据卷 |
列出数据卷 | docker volume ls | 查看所有已存在的数据卷 |
查看数据卷详情 | docker volume inspect myvol | 查看数据卷的元数据及宿主机路径 |
删除数据卷 | docker volume rm myvol | 删除指定数据卷(需先删除使用它的容器) |
挂载数据卷到容器 | -v myvol:/path/in/container | 在运行容器时挂载数据卷 |
多个数据卷挂载 | -v vol1:/path1 -v vol2:/path2 | 支持同时挂载多个数据卷 |
创建一个名为
app_logs
的数据卷,并挂载到容器的/app/logs
路径下。向数据卷中写入日志文件,然后删除容器再重建,验证日志是否还在。
尝试使用两个不同的容器挂载同一个数据卷,实现数据共享。
三:目录挂载(Bind Mounts)
Bind mounts
允许你将宿主机上的一个文件或目录直接挂载到容器中。
它不同于命名数据卷,因为它直接指向宿主机上的具体路径。
特点:
-
灵活性高:可以指定任意宿主机路径
-
实时同步:对挂载目录的任何更改都会立即反映在容器和宿主机之间
-
适合开发环境:便于快速迭代代码或配置文件
与 Named Volumes 对比:
卷 |Docker 文档
数据卷(Named Volumes):是 Docker 内部管理的一种持久化存储机制,独立于容器的生命周期存在。它通常位于 /var/lib/docker/volumes/
下,并且由 Docker 自动管理。
目录挂载(Bind Mounts):允许你将宿主机上的任意文件或目录直接挂载到容器中。这意味着你可以指定具体的宿主机路径,而不是让 Docker 自动管理路径。
数据卷的优势
-
自动化管理:由 Docker 自动管理路径和生命周期,减少了手动干预。
-
更好的隔离性:适合生产环境下的多用户或多应用部署。
-
更高的性能:绕过了联合文件系统,提供了更快的读写速度。
目录挂载的优势
-
灵活性:能够直接操作宿主机文件系统,适合开发调试和特定路径需求。
-
实时更新:任何对宿主机文件的修改都会即时反映在容器内,非常适合快速迭代。
-
精确控制:可以将容器内的目录映射到宿主机上的任意位置,满足复杂的部署需求。
1、基本操作
挂载目录到容器:
-v
这是较早的方式,语法相对简单直接当你指定一个宿主机路径(如
/home/user/data
)时,它会创建一个绑定挂载(bind mount)。如果只提供一个名称(如
my_volume
),则 Docker 会将其视为一个命名卷
docker run -v /host/path:/container/path ...
--mount
选项这是更新的方式,提供了更清晰、更详细的语法,并且支持更多选项
docker run --mount type=bind,source=<host_path>,target=<container_path> ...
或者用于命名卷:
docker run --mount type=volume,source=my_volume,target=/path/in/container ...
-
type
可以是bind
或volume
,分别表示绑定挂载和命名卷。 -
source
或src
指定宿主机上的源路径(对于绑定挂载)或卷名(对于命名卷)。 -
target
或dst
指定容器内的目标路径。
优点:
-
更加明确,尤其是在处理复杂的挂载需求时(例如设置只读权限、传播模式等)。
-
支持更多的高级功能,比如 SELinux 标签、只读模式等。
使用 -v
创建绑定挂载:
docker run -d \--name mynginx \-v $(pwd)/html:/usr/share/nginx/html \nginx
使用 --mount
创建相同的绑定挂载:
docker run -d \--name mynginx \--mount type=bind,source=$(pwd)/html,target=/usr/share/nginx/html \nginx
使用 -v
创建命名卷:
docker run -d \--name mynginx \-v nginx_html:/usr/share/nginx/html \nginx
使用 --mount
创建相同的命名卷:
docker run -d \--name mynginx \--mount type=volume,source=nginx_html,target=/usr/share/nginx/html \nginx
参数名 | 含义 | 示例 |
---|---|---|
type | 挂载类型,这里是 bind | type=bind |
source 或 src | 宿主机上要挂载的文件或目录路径 | source=/home/user/html |
target 或 dst | 容器内挂载的目标路径 | target=/usr/share/nginx/html |
readonly | 设置为只读模式(可选) | 添加 readonly 表示只读 |
挂载多个目录
docker run -d \--name mynginx \--mount type=bind,source=$(pwd)/html,target=/usr/share/nginx/html \--mount type=bind,source=$(pwd)/conf.d,target=/etc/nginx/conf.d \--mount type=bind,source=$(pwd)/logs,target=/var/log/nginx \-p 80:80 \nginx
适用于需要多目录挂载的复杂服务。
设置只读挂载
docker run -d \--name mynginx \--mount type=bind,source=$(pwd)/html,target=/usr/share/nginx/html,readonly \-p 80:80 \nginx
-
容器内的
/usr/share/nginx/html
目录变为只读 -
防止容器程序意外修改宿主机文件
2、Nginx + 目录挂载
创建并启动容器
mkdir -p html
echo "Hello from Bind Mount" > html/index.htmldocker run -d \--name mynginx \--mount type=bind,source=$(pwd)/html,target=/usr/share/nginx/html \-p 80:80 \nginx
验证页面内容
访问浏览器:http://localhost/
应能看到 “Hello from Bind Mount”
修改宿主机文件并验证同步
编辑 html/index.html
,例如改为:
<h1>Hello again from Bind Mount</h1>
刷新浏览器页面,应能看到更新内容。
删除容器并重建
docker stop mynginx && docker rm mynginxdocker run -d \--name mynginx_new \--mount type=bind,source=$(pwd)/html,target=/usr/share/nginx/html \-p 80:80 \nginx
再次访问页面,内容依然存在。
3、MySQL + 目录挂载
步骤 1:创建并启动容器
mkdir -p mysql_datadocker run -d \--name mysql_db \--mount type=bind,source=$(pwd)/mysql_data,target=/var/lib/mysql \-e MYSQL_ROOT_PASSWORD=123456 \mysql:8.0.22
步骤 2:初始化数据库
docker exec -it mysql_db bash
mysql -uroot -p123456CREATE DATABASE testdb;
USE testdb;CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(100)
);INSERT INTO users (name) VALUES ('Alice'), ('Bob');
步骤 3:删除容器并重建
docker stop mysql_db && docker rm mysql_dbdocker run -d \--name mysql_db_new \--mount type=bind,source=$(pwd)/mysql_data,target=/var/lib/mysql \-e MYSQL_ROOT_PASSWORD=123456 \mysql:8.0.22
查询验证数据是否保留:
docker exec -it mysql_db_new mysql -uroot -p123456 -e "SELECT * FROM testdb.users;"
4、静态网页服务 + 实时更新
场景描述:
你是一个运维人员,负责部署一个简单的静态网站。你想把网页文件放在宿主机上,并随时更新它们,而不需要每次重启容器。
步骤 1:创建本地网页目录
mkdir -p /opt/myweb
cd /opt/myweb
echo "<h1>Welcome to My Website</h1>" > index.html
步骤 2:启动容器并挂载网页目录
docker run -d \
--name myhttpd \
--mount type=bind,source=/opt/myweb,target=/usr/local/apache2/htdocs \
-p 80:80 \
httpd
Apache 默认的网页根目录是
/usr/local/apache2/htdocs
步骤 3:访问页面验证
打开浏览器访问:
http://localhost/
应能看到 “Welcome to My Website”
步骤 4:修改网页内容并验证同步
编辑宿主机上的网页:
echo "<h1>Hello from Bind Mount</h1>" > /opt/myweb/index.html
刷新浏览器,应能看到新内容!
Apache HTTPD 默认监听端口是 80
,它的监听端口是在配置文件中定义的,通常是
`/usr/local/apache2/conf/httpd.conf`。
创建本地目录并准备配置文件
mkdir -p /opt/httpd-conf/conf/
你可以先从一个正在运行的 Apache 容器中复制出默认的配置,再修改它。
创建一个临时容器:
docker run -d --name temp_httpd httpd
复制默认配置到宿主机:
docker cp temp_httpd:/usr/local/apache2/conf /opt/httpd-conf
停止并删除临时容器:
docker stop temp_httpd && docker rm temp_httpd
现在 /opt/httpd-conf
中就包含了完整的 Apache 配置文件。
修改 httpd.conf
设置监听端口为 8080
编辑配置文件:
vim /opt/httpd-conf/conf/httpd.conf
找到这一行:
Listen 80
改为:
Listen 8080
保存退出。
启动容器时挂载配置文件目录
docker run -d \--name myhttpd \--mount type=bind,source=/opt/httpd-conf/conf/,target=/usr/local/apache2/conf \-p 8080:8080 \httpd
使用 --mount
挂载单个文件
基本语法
docker run --mount type=bind,source=<宿主机文件路径>,target=<容器内文件路径> <镜像名>
-
type=bind
:指定挂载类型为绑定挂载。 -
source
或src
:宿主机上的源文件路径。 -
target
或dst
:容器内的目标文件路径。
挂载单个配置文件到 Apache HTTPD 容器中
假设我们有一个自定义的 httpd.conf
文件,并希望将其挂载到运行中的 Apache HTTPD 容器中以替换默认的配置文件。
使用以下命令启动 Apache HTTPD 容器,并通过 --mount
将宿主机上的单个配置文件挂载到容器内的相应位置:
docker run -d \--name myhttpd \--mount type=bind,source=/opt/httpd-conf/conf/httpd.conf,target=/usr/local/apache2/conf/httpd.conf \-p 8080:8080 \httpd
我们挂载了 /opt/httpd-conf/httpd.conf
到容器内的 /usr/local/apache2/conf/httpd.conf
路径。
这样做的结果是,Apache HTTPD 在容器中会使用你提供的配置文件,而不是默认的那个。
5、日志共享 + 多容器集中查看
场景描述:
你需要让多个 Web 服务容器的日志都输出到同一个目录,方便统一管理和分析。
编辑配置文件,加入
CustomLog "logs/access_log" common
ErrorLog "logs/error_log"
步骤 1:创建共享日志目录
mkdir -p /data/logs/weblogs
步骤 2:启动第一个 Apache 容器
# 容器 web1 挂载 log1 子目录
docker run -d \--name web1 \--mount type=bind,source=/data/logs/weblogs/log1,target=/usr/local/apache2/logs \--mount type=bind,source=/opt/httpd-conf/conf/httpd.conf,target=/usr/local/apache2/conf/httpd.conf \-p 80:80 \httpd
步骤 3:启动第二个 Apache 容器
# 容器 web2 挂载 log2 子目录
docker run -d \--name web2 \--mount type=bind,source=/data/logs/weblogs/log2,target=/usr/local/apache2/logs \--mount type=bind,source=/opt/httpd-conf/conf/httpd.conf,target=/usr/local/apache2/conf/httpd.conf \-p 8080:80 \httpd
/data/logs/weblogs/
├── log1/
│ ├── access_log
│ └── error_log
├── log2/
│ ├── access_log
│ └── error_log
步骤 4:查看共享日志
ls /data/logs/weblogstail -f /data/logs/weblogs/access_log