DevToolBox免费
博客

Docker Volumes vs Bind Mounts 详解

9 分钟阅读作者 DevToolBox

Docker 容器的设计是临时性的——容器被删除时,其中所有数据都会丢失。Docker 提供三种存储机制来将数据持久化到容器生命周期之外:卷(volumes)、绑定挂载(bind mounts)和 tmpfs 挂载。本指南涵盖 Docker 存储的方方面面,从基本卷命令到高级驱动配置、备份策略和性能调优。

存储类型概览

Docker 为容器提供三种主要存储选项。了解它们的区别对于为每个场景选择正确的方案至关重要。

存储类型管理者主机位置最佳用途
VolumeDocker/var/lib/docker/volumes/生产数据、数据库
Bind Mount用户任意宿主机路径开发环境、配置文件
tmpfsDocker内存(RAM)密钥、临时数据(仅 Linux)
# Comparison of storage types in docker run
# Named volume
docker run -v mydata:/app/data nginx

# Bind mount
docker run -v /home/user/data:/app/data nginx

# tmpfs mount
docker run --tmpfs /app/temp nginx

# Using --mount flag (more explicit)
docker run --mount type=volume,source=mydata,target=/app/data nginx
docker run --mount type=bind,source=/home/user/data,target=/app/data nginx
docker run --mount type=tmpfs,target=/app/temp nginx
提示: 卷是 Docker 推荐的数据持久化机制。与绑定挂载不同,卷完全由 Docker 管理,与宿主机文件系统隔离。

命名卷

命名卷是 Docker 中最常用且推荐的数据持久化方式。Docker 管理卷的完整生命周期,包括创建、存储位置和清理。

创建和使用命名卷

# Create a named volume
docker volume create mydata

# Create with specific driver and options
docker volume create --driver local \
  --opt type=none \
  --opt device=/path/to/data \
  --opt o=bind \
  my-bind-vol

# Use a named volume in docker run
docker run -d \
  --name postgres-db \
  -v pgdata:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=secret \
  postgres:16

# Volume is auto-created if it doesn't exist
docker run -d -v mydata:/data alpine

# Use --mount for explicit volume creation
docker run -d \
  --mount type=volume,source=mydata,target=/data \
  alpine

# Inspect volume details
docker volume inspect mydata
# Output:
# [
#   {
#     "CreatedAt": "2025-01-15T10:30:00Z",
#     "Driver": "local",
#     "Labels": {},
#     "Mountpoint": "/var/lib/docker/volumes/mydata/_data",
#     "Name": "mydata",
#     "Options": {},
#     "Scope": "local"
#   }
# ]

卷生命周期

命名卷独立于容器持久化。它们在容器停止、重启和删除后仍然存在。删除命名卷的唯一方式是通过 docker volume rm 或 docker volume prune 显式删除。

# Create container with volume
docker run -d --name app1 -v shared-data:/data alpine sleep 3600

# Write data inside the volume
docker exec app1 sh -c 'echo "Hello from app1" > /data/message.txt'

# Stop and remove the container
docker stop app1 && docker rm app1

# Data persists! Create new container with same volume
docker run --rm -v shared-data:/data alpine cat /data/message.txt
# Output: Hello from app1

# Multiple containers can share the same volume
docker run -d --name writer -v shared-data:/data alpine sh -c \
  'while true; do date >> /data/log.txt; sleep 5; done'
docker run -d --name reader -v shared-data:/data:ro alpine tail -f /data/log.txt

绑定挂载

绑定挂载将宿主机的特定目录或文件直接映射到容器中。宿主机路径必须是绝对路径。绑定挂载非常适合需要实时代码重载的开发工作流。

绑定挂载语法

# Short syntax: -v host_path:container_path
docker run -v /home/user/project:/app node:20

# Short syntax with read-only
docker run -v /home/user/config:/etc/app/config:ro nginx

# Long syntax: --mount (recommended for clarity)
docker run --mount type=bind,source=/home/user/project,target=/app node:20

# --mount with read-only
docker run \
  --mount type=bind,source=/home/user/config,target=/etc/app/config,readonly \
  nginx

# Key difference: -v creates the host dir if missing
# --mount fails with an error if host dir doesn't exist
docker run -v /nonexistent/path:/data alpine    # Creates /nonexistent/path
docker run --mount type=bind,source=/nonexistent/path,target=/data alpine  # ERROR

使用绑定挂载的开发工作流

绑定挂载在开发中表现出色——在宿主机上编辑代码,更改立即出现在容器内,无需重建镜像。

# React development with hot reload
docker run -d \
  --name react-dev \
  -v $(pwd)/src:/app/src \
  -v $(pwd)/public:/app/public \
  -p 3000:3000 \
  -e CHOKIDAR_USEPOLLING=true \
  my-react-app

# Python Flask development
docker run -d \
  --name flask-dev \
  -v $(pwd):/app \
  -p 5000:5000 \
  -e FLASK_DEBUG=1 \
  my-flask-app

# Go development with air (live reload)
docker run -d \
  --name go-dev \
  -v $(pwd):/app \
  -p 8080:8080 \
  cosmtrek/air
警告: 警告:绑定挂载允许容器直接访问宿主机文件系统。以 root 运行的容器可以修改或删除宿主机文件。当容器只需要读取数据时,使用只读挂载(:ro)。

Docker Compose 卷

Docker Compose 提供声明式方式来定义服务的卷。卷可以使用短语法或长语法来定义,长语法提供更多控制选项。

短语法

services:
  db:
    image: postgres:16
    volumes:
      # Named volume
      - pgdata:/var/lib/postgresql/data

      # Bind mount (relative path)
      - ./init-scripts:/docker-entrypoint-initdb.d

      # Bind mount (absolute path)
      - /var/log/postgres:/var/log/postgresql

      # Read-only bind mount
      - ./config/postgresql.conf:/etc/postgresql/postgresql.conf:ro

      # Anonymous volume
      - /var/lib/postgresql/temp

volumes:
  pgdata:           # Named volume declaration
    driver: local

长语法

长语法提供更多配置选项,包括只读模式、卷子路径和绑定传播。

services:
  web:
    image: nginx:alpine
    volumes:
      # Long syntax - named volume
      - type: volume
        source: web-data
        target: /usr/share/nginx/html
        volume:
          nocopy: true    # Don't copy container data into volume

      # Long syntax - bind mount
      - type: bind
        source: ./nginx.conf
        target: /etc/nginx/nginx.conf
        read_only: true

      # Long syntax - tmpfs
      - type: tmpfs
        target: /tmp
        tmpfs:
          size: 100000000  # 100MB limit

volumes:
  web-data:

外部卷和共享卷

使用外部卷在多个 Compose 项目之间共享数据,或引用在 Compose 之外创建的卷。

# Project A: creates the volume
# docker-compose.yml (Project A)
services:
  api:
    image: my-api
    volumes:
      - shared-data:/data

volumes:
  shared-data:
    name: my-shared-data    # Explicit name (not prefixed with project name)

---
# Project B: uses the same volume
# docker-compose.yml (Project B)
services:
  worker:
    image: my-worker
    volumes:
      - shared-data:/data

volumes:
  shared-data:
    external: true
    name: my-shared-data    # Must match the volume name from Project A

---
# Pre-create a volume externally
docker volume create --driver local my-shared-data

卷驱动

Docker 支持可插拔的卷驱动,允许将数据存储在远程主机或云服务商。默认驱动是 "local",将数据存储在 Docker 宿主机上。

带选项的本地驱动

# Local driver with tmpfs backend (RAM-based volume)
docker volume create --driver local \
  --opt type=tmpfs \
  --opt device=tmpfs \
  --opt o=size=256m \
  ramdisk

# Local driver with ext4 filesystem on a specific device
docker volume create --driver local \
  --opt type=ext4 \
  --opt device=/dev/sdb1 \
  fast-storage

# Local driver binding to a specific directory
docker volume create --driver local \
  --opt type=none \
  --opt device=/mnt/data/app \
  --opt o=bind \
  app-data

NFS 卷驱动

# NFS volume using the local driver
docker volume create --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw,nfsvers=4 \
  --opt device=:/exports/data \
  nfs-data

# NFS volume in Docker Compose
services:
  app:
    image: my-app
    volumes:
      - nfs-data:/app/data

volumes:
  nfs-data:
    driver: local
    driver_opts:
      type: nfs
      o: addr=192.168.1.100,rw,nfsvers=4
      device: ":/exports/data"

# CIFS/SMB volume (Windows shares)
docker volume create --driver local \
  --opt type=cifs \
  --opt device=//server/share \
  --opt o=addr=server,username=user,password=pass \
  smb-data

云卷驱动

在云端生产工作负载中,使用特定云服务商的卷驱动将云存储直接挂载到容器中。

# AWS EFS volume (using amazon-ecs-volume-plugin or docker plugin)
# First install the plugin
docker plugin install rexray/efs

# Create volume backed by EFS
docker volume create --driver rexray/efs \
  --opt filesystem=fs-12345678 \
  efs-data

# Azure Files in Docker Compose
volumes:
  azure-data:
    driver: azure_file
    driver_opts:
      share_name: myshare
      storage_account_name: mystorageaccount

# GCP Filestore / persistent disk (via CSI or plugin)
# Typically used with Kubernetes rather than standalone Docker

只读卷

:ro 标志(或长语法中的 read_only 选项)将卷以只读方式挂载到容器内。这是关键的安全实践——如果容器被攻破,它无法修改挂载的数据。

# Read-only with -v flag
docker run -v myconfig:/etc/app/config:ro nginx

# Read-only with --mount
docker run \
  --mount type=volume,source=myconfig,target=/etc/app/config,readonly \
  nginx

# Read-only bind mount
docker run -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro nginx

# Docker Compose - short syntax
services:
  web:
    volumes:
      - ./config:/etc/app/config:ro
      - certs:/etc/ssl/certs:ro

# Docker Compose - long syntax
services:
  web:
    volumes:
      - type: bind
        source: ./config
        target: /etc/app/config
        read_only: true

# Entire container filesystem as read-only (--read-only flag)
docker run --read-only \
  --tmpfs /tmp \
  --tmpfs /run \
  -v data:/app/data \
  my-app

只读挂载的安全优势

  • 防止容器修改配置文件
  • 保护宿主机文件系统免受被攻破容器的影响
  • 强制执行不可变基础设施模式
  • 减少容器逃逸漏洞的影响范围

数据持久化

了解数据何时持久化、何时丢失,对于可靠的容器部署至关重要。

场景卷数据绑定挂载数据容器数据
容器停止保留保留保留
容器重启保留保留保留
容器删除保留保留丢失
docker compose down保留保留丢失
docker compose down -v丢失保留丢失
docker volume prune删除(未使用的)保留N/A

备份与恢复

Docker 没有提供内置的卷备份命令。标准方法是使用临时容器挂载卷并创建 tar 归档。

备份卷

# Backup a named volume to a tar.gz file
docker run --rm \
  -v mydata:/source:ro \
  -v $(pwd)/backups:/backup \
  alpine tar czf /backup/mydata-$(date +%Y%m%d).tar.gz -C /source .

# Backup PostgreSQL volume (prefer pg_dump for consistency)
docker exec postgres-db pg_dump -U postgres mydb > backup.sql

# Backup MySQL volume
docker exec mysql-db mysqldump -u root -p mydb > backup.sql

# Backup with compression and timestamp
docker run --rm \
  -v pgdata:/source:ro \
  -v $(pwd)/backups:/backup \
  alpine sh -c 'tar czf /backup/pgdata-$(date +%Y%m%d-%H%M%S).tar.gz -C /source .'

恢复卷

# Restore a volume from backup
docker volume create mydata-restored

docker run --rm \
  -v mydata-restored:/target \
  -v $(pwd)/backups:/backup:ro \
  alpine sh -c 'cd /target && tar xzf /backup/mydata-20250115.tar.gz'

# Restore PostgreSQL from SQL dump
docker exec -i postgres-db psql -U postgres mydb < backup.sql

# Clone a volume (backup + restore in one step)
docker run --rm \
  -v source-vol:/source:ro \
  -v target-vol:/target \
  alpine sh -c 'cd /source && tar cf - . | (cd /target && tar xf -)'

使用 --volumes-from 备份

--volumes-from 标志挂载另一个容器的所有卷,当你不知道确切的卷挂载路径时非常有用。

# Backup all volumes from a running container
docker run --rm \
  --volumes-from my-running-container \
  -v $(pwd)/backups:/backup \
  alpine tar czf /backup/all-volumes.tar.gz \
    /var/lib/postgresql/data \
    /etc/app/config

# This is useful when containers have multiple volumes
# and you want a single backup of all data

自动备份脚本

#!/bin/bash
# backup-volumes.sh - Automated Docker volume backup
BACKUP_DIR="/backups/docker"
RETENTION_DAYS=30
DATE=$(date +%Y%m%d-%H%M%S)

# Backup each named volume
for vol in $(docker volume ls -q); do
  echo "Backing up volume: $vol"
  docker run --rm \
    -v "$vol":/source:ro \
    -v "$BACKUP_DIR":/backup \
    alpine tar czf "/backup/$vol-$DATE.tar.gz" -C /source .
done

# Clean up old backups
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup complete: $(ls -1 $BACKUP_DIR/*$DATE* | wc -l) volumes backed up"

# Add to crontab for daily backups at 2 AM:
# 0 2 * * * /path/to/backup-volumes.sh >> /var/log/docker-backup.log 2>&1

卷管理命令

Docker 提供了一套完整的 CLI 命令来管理卷的整个生命周期。

命令说明
docker volume create mydata创建命名卷
docker volume ls列出所有卷
docker volume ls -f dangling=true列出未被任何容器使用的卷
docker volume inspect mydata显示卷的详细信息
docker volume rm mydata删除指定卷(必须未被使用)
docker volume rm $(docker volume ls -q)删除所有卷(危险!)
docker volume prune删除所有未使用的卷
docker volume prune -f强制删除未使用的卷(无确认)
docker system df -v显示卷磁盘使用情况
docker inspect -f '{{.Mounts}}' container查看容器的挂载信息

性能考量

卷性能在不同操作系统上差异显著。在 Linux 上,卷和绑定挂载都能达到接近原生的性能。在 macOS 和 Windows 上,有一些重要区别。

Linux

卷和绑定挂载都使用原生文件系统。性能与直接访问宿主机相同。这是生产环境 Docker 工作负载的推荐平台。

macOS 和 Windows

Docker Desktop 在轻量级虚拟机内运行 Linux 容器。宿主机和虚拟机之间的文件共享引入了 I/O 开销,特别是对于包含大量小文件的绑定挂载(如 node_modules)。

# Problem: node_modules in bind mount is extremely slow on macOS
# BAD - entire project as bind mount (slow npm install, slow builds)
docker run -v $(pwd):/app node:20 npm install
# This syncs thousands of node_modules files between host and VM

# GOOD - use named volume for node_modules
docker run \
  -v $(pwd):/app \
  -v node_modules:/app/node_modules \
  node:20 npm install
# node_modules stays inside the VM, only source code is synced

一致性标志(macOS)

macOS 上的 Docker 支持一致性标志来调整性能与数据一致性之间的权衡。注意:这些标志在使用 VirtioFS 的新版 Docker Desktop 中已弃用。

标志行为使用场景
consistent完全一致(默认),宿主机和容器始终看到相同内容需要强一致性时
cached宿主机写入最终会在容器中可见(有延迟)源代码(宿主机编辑,容器读取)
delegated容器写入最终会在宿主机上可见(有延迟)构建输出(容器写入,宿主机读取)
# Legacy consistency flags (deprecated with VirtioFS)
docker run -v $(pwd)/src:/app/src:cached node:20
docker run -v $(pwd)/dist:/app/dist:delegated node:20

# Modern Docker Desktop (4.15+) uses VirtioFS by default
# which provides near-native performance without flags
# Check: Docker Desktop → Settings → General → Virtual file sharing

性能建议

  • 使用命名卷来存储 node_modules,避免跨文件系统同步
  • 在 macOS/Windows 上,使用 VirtioFS(Docker Desktop 4.15+ 默认)获取最佳绑定挂载性能
  • 使用命名卷覆盖层将大型依赖目录从绑定挂载中排除
  • 对于数据库卷,始终使用命名卷而非绑定挂载

常见模式

以下是常见 Docker 存储场景中经过实战检验的模式。

数据库数据持久化

# PostgreSQL with persistent data
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    restart: unless-stopped

  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: myapp
    volumes:
      - mysqldata:/var/lib/mysql
    restart: unless-stopped

  mongo:
    image: mongo:7
    volumes:
      - mongodata:/data/db
      - mongoconfig:/data/configdb
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redisdata:/data
    restart: unless-stopped

volumes:
  pgdata:
  mysqldata:
  mongodata:
  mongoconfig:
  redisdata:

共享配置文件

# Share config files across multiple services
services:
  web:
    image: nginx:alpine
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/certs:/etc/nginx/certs:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - web-logs:/var/log/nginx

  app:
    build: .
    volumes:
      - ./config/app.yml:/app/config/app.yml:ro
      - app-logs:/app/logs

  # Log aggregator reads logs from both services
  logrotate:
    image: alpine
    volumes:
      - web-logs:/logs/nginx:ro
      - app-logs:/logs/app:ro

volumes:
  web-logs:
  app-logs:

开发热重载与依赖隔离

此模式绑定挂载源代码以实现实时重载,同时使用命名卷存储 node_modules 以避免性能问题和平台特定的二进制不兼容。

# Development setup with hot-reload + fast dependencies
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      # Bind mount source code for hot-reload
      - ./src:/app/src
      - ./public:/app/public
      - ./package.json:/app/package.json

      # Named volume for node_modules (performance + isolation)
      - node_modules:/app/node_modules

      # Exclude build artifacts
      - dist:/app/dist
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development

volumes:
  node_modules:    # Stays in Docker VM, no host sync overhead
  dist:            # Build output stays fast

# After changing package.json, rebuild:
# docker compose run --rm app npm install
# docker compose up

构建产物输出

使用命名卷在构建容器和 Web 服务容器之间共享构建产物。

# Multi-stage build with artifact sharing
services:
  builder:
    build:
      context: .
      target: builder
    volumes:
      - build-output:/app/dist

  web:
    image: nginx:alpine
    volumes:
      - build-output:/usr/share/nginx/html:ro
    ports:
      - "80:80"
    depends_on:
      builder:
        condition: service_completed_successfully

volumes:
  build-output:

相关工具

使用我们的 Docker Compose 生成器可视化生成 Docker Compose 配置:

Docker Compose 生成器

常见问题

Docker 卷和绑定挂载有什么区别?

卷由 Docker 管理,存储在 Docker 控制的宿主机文件系统区域(/var/lib/docker/volumes/)。绑定挂载将特定的宿主机路径直接映射到容器中。卷更具可移植性、更易于备份,且跨平台表现一致。绑定挂载依赖宿主机文件系统结构,主要用于开发工作流。

Docker 卷在容器删除后还存在吗?

是的。命名卷独立于容器持久化。使用 docker rm 删除容器不会删除其卷。卷只有在通过 docker volume rm、docker volume prune 或 docker compose down -v 显式删除时才会被移除。匿名卷(未命名创建的)可能会被 docker rm -v 或 docker volume prune 删除。

如何备份 Docker 卷?

使用临时容器创建卷内容的 tar 归档:docker run --rm -v myvolume:/data -v $(pwd):/backup alpine tar czf /backup/myvolume-backup.tar.gz -C /data . 这将挂载卷和当前目录,然后创建压缩归档。对于数据库,建议使用数据库原生导出工具(pg_dump、mysqldump)进行一致性备份。

为什么 Docker 卷在 macOS 上很慢?

macOS 上的 Docker Desktop 在轻量级虚拟机内运行 Linux 容器。绑定挂载需要在 macOS 宿主机和 Linux 虚拟机之间同步文件,引入了 I/O 开销。这在大量小文件(如 node_modules)时特别明显。解决方案包括:对依赖目录使用命名卷、在 Docker Desktop 设置中启用 VirtioFS、减少绑定挂载中的文件数量。

开发时应该用卷还是绑定挂载?

对源代码使用绑定挂载,这样宿主机上的更改会立即反映到容器中。对依赖目录(node_modules、vendor、venv)使用命名卷,以避免性能问题和平台特定的二进制兼容问题。这种混合方式既能让代码实时重载,又能保持依赖快速且隔离。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

🐳Docker Compose GeneratorYMLYAML Validator & FormatterY{}JSON ↔ YAML Converter

相关文章

Docker Compose 速查表:服务、卷和网络

Docker Compose 核心参考:服务定义、卷挂载、网络配置、环境变量和常见栈示例。

Dockerfile 最佳实践与多阶段构建

编写优化的 Dockerfile:多阶段构建、层缓存、安全加固和精简镜像。包含 Node.js、Python 和 Go 的实用示例。