引言

Docker 是一个开源的容器化平台,它允许开发者将应用程序及其依赖打包成一个轻量级、可移植的容器,然后发布到任何流行的 Linux 或 Windows 机器上。容器之间相互隔离,且与宿主机共享操作系统内核,因此比传统的虚拟机更轻量、启动更快。


优势

  • 环境一致性:解决了“在我机器上能跑,在你机器上就不行”的问题。通过将应用和环境一起打包,你可以确保应用在任何地方都以相同的方式运行。

  • 快速部署:容器启动只需秒级,比虚拟机分钟级的启动速度快得多。

  • 资源利用率高:容器共享宿主机内核,无需为每个应用分配完整的操作系统,因此磁盘占用小,内存和CPU消耗更低。

  • 易于扩展:结合容器编排工具(如 Kubernetes、Docker Swarm),可以轻松实现应用的横向扩展。

  • 版本控制和可复用性:Docker 镜像可以像代码一样进行版本管理,并可以分享到公共或私有仓库,方便团队协作。


主要概念

1. 镜像

镜像是一个只读模板,包含了运行应用所需的文件系统、依赖库、环境变量和启动命令。可以理解为容器的“源代码”。
镜像不包含任何动态数据,其内容在构建之后也不会被改变。
镜像可以通过Dockerfile定义,并可以从Docker Hub等公共或私有仓库中获取。
Docker 在设计时,就充分利用分层存储(Union FS)的技术,将其设计为分层存储的架构。

分层存储(Union FS):即 统一文件系统(Union File System)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在 用户 的角度看来,只存在一个文件系统。

2. 容器

镜像的运行实例。你可以启动、停止、删除容器,容器共享宿主机的操作系统内核,但彼此之间是隔离的。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行在属于自己的独立的命名空间。因此容器可以拥有自己的root文件系统、自己的网络配置、自己的进程空问,甚至自己的用户空间。

前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个是为容器运行时读写而准备的存储层为容器存储层(读写层)。

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。

数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。

3. 仓库

存放镜像的地方。镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要这样一个集中的存储、分发镜像的服务。最著名的公共仓库是 Docker Hub。你可以从仓库拉取镜像,也可以将自己构建的镜像推送到仓库。


常用命令

Docker有非常丰富的命令,以下是一些常用的命令。

1. 基础命令

​docker version     #显示docker详细信息
docker info       #显示docker的系统信息,包括镜像和容器的数量
docker --help     #docker帮助命令手册

2. 镜像命令

docker images  #查看所有本地主机的镜像
docker search 镜像名           #搜索镜像
docker pull 镜像名:[标签]      #下载镜像(如果不写tag,默认是latest)
docker pull --platform <平台架构> 镜像名:[标签] #拉取特定架构的镜像,linux/amd64:拉取 x86_64 (amd64) 架构的镜像;linux/arm64:拉取 ARM64 (aarch64) 架构的镜像
docker inspect 镜像名:[标签] | grep Architecture #确认镜像的实际架构
docker push 镜像名            #推送镜像
docker rmi 镜像名 [标签]       #删除镜像    docker rmi -f $(docker images -aq)  删除全部镜像
docker tag  镜像名:版本   新镜像名:版本    #复制镜像并且修改名称
docker commit  -a "xxx"  -c "xxx" 镜像ID 名字:版本   #提交镜像 
-a :提交的镜像作者;
-c :使用Dockerfile指令来创建镜像;
-m :提交时的说明文字;
 
docker load -i    /xxx/xxx.tar         #导入镜像
docker save -o   /xxx/xxx.tar          #保存一个镜像为一个tar包

镜像上传示例

docker load -i example_image.tar
docker login -u user -p pwd repo_domain
docker tag image_id example_repository:example_tag
docker push example_repository:example_tag

3. 容器命令

docker run [可选参数] image 命令 #启动容器(无镜像会先下载镜像)
#参数说明
--name = "Name"   容器名字
-c   后面跟待完成的命令
-d   以后台方式运行并且返回ID,启动守护进程式容器
-i   使用交互方式运行容器,通常与t同时使用
-t   为容器重新分配一个伪输入终端。也即启动交互式容器
-p   指定容器端口    -p 容器端口:物理机端口  映射端口
-P   随机指定端口
-v   给容器挂载存储卷
 
docker build  #创建镜像        -f:指定dockerfile文件路径   -t:镜像名字以及标签
docker logs 容器实例的ID          #查看容器日志
docker rename 旧名字  新名字      # 给容器重新命名
docker top    容器实例的ID                  #查看容器内进程
docker ps -a                    #列出所有容器(不加-a就是在运行的)
docker rm      容器实例的ID                 #删除容器(正在运行容器不能删除,除非加-f选项)
docker kill  容器实例的ID        #杀掉容器
docker history   容器实例的ID    #查看docker镜像的变更历史
docker start 容器实例的ID        #启动容器
docker restart 容器实例的ID       #重启容器
docker stop 容器实例的ID         #停止正在运行的容器
docker attach /docker exec  容器实例的ID   #同为进入容器命令,不同的是attach连接终止会让容器退出后台运行,而exec不会。并且,docker attach是进入正在执行的终端,不会情动新的进程,而docker exec则会开启一个新的终端,可以在里面操作。
docker image inspect  容器名称:容器标签       #查看容器内源数据
docker cp  容器id:容器内路径   目的主机路径           #从容器内拷贝文件到主机(常用)或者从主机拷贝到容器(一般用挂载)
exit                           #直接退出容器 
crlt + P + Q                   #退出容器但是不终止运行

容器数据管理

docker容器在产生数据的时候,如果不通过docker commit生成新的镜像,使得数据作为镜像的一部分保存下来,那么当容器删除之后,数据自然而然的也会消失。
为了持久化数据或在多个容器之间共享数据,有两种方式可以实现:数据卷、绑定挂载。

1. 数据卷(Volume)

概念

卷就是目录或者文件,存在一个或者多个容器之中,由docker挂载到容器,但是不属于联合文件系统,因此能够绕过UFS提供一些用于持续存储或者共享数据的特性。

卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此docker不会在容器删除时删除其挂载的数据卷。


特性

数据卷具有以下特性:

  • 数据卷可以在容器之间共享和重用;
  • 对数据卷的修改可以直接生效;
  • 更新数据卷不会影响镜像;
  • 数据卷默认会一直存在,即使容器被删除。

使用

  1. 创建和管理数据卷
# 创建一个数据卷
docker volume create my-data

# 列出所有数据卷
docker volume ls

# 查看数据卷详情
docker volume inspect my-data

# 删除数据卷
docker volume rm my-data
  1. 挂载数据卷
# 使用 --mount 或 -v 参数挂载数据卷
docker run -d -p 8080:80 --name my-nginx --mount source=my-data,target=/usr/share/nginx/html nginx
# 或使用旧式语法 -v my-data:/usr/share/nginx/html
docker run -d -p 8080:80 --name my-nginx -v my-data:/usr/share/nginx/html nginx

这样,容器内的 /usr/share/nginx/html 目录就对应到了宿主机的 my-data 数据卷中。即使删除容器,数据卷中的数据依然保留,可以重新挂载到新容器。

假设我们运行一个 MySQL 容器,需要持久化数据库文件:

# 创建数据卷
docker volume create mysql-data

# 运行 MySQL 容器,挂载数据卷到 /var/lib/mysql
docker run -d --name mysql-db -e MYSQL_ROOT_PASSWORD=my-secret-pw -v mysql-data:/var/lib/mysql mysql:8.0

2. 绑定挂载(bind mounts)

概念

绑定挂载是Docker早期提供的数据持久化存储方式,是将宿主机的目录/文件直接挂载到容器中,并绑定在容器指定的目录/文件上。这种方式使得宿主机上的文件系统可以直接被容器访问和修改。


特性

  • 挂载目录/文件非常方便,但文件的备份跟迁移相对麻烦;
  • 挂载的目录/文件无法通过Docker本身进行管理;
  • 挂载的目录/文件使用的磁盘空间可能会受其他程序影响;
  • 挂载的目录/文件可以便捷的在宿主机上进行查看及管理。

数据卷和绑定挂载区别

数据卷和绑定挂载都用于将宿主机上的目录或文件挂载到容器中,从而实现数据的持久化、共享和备份。但二者在管理方式和使用场景等方面存在区别。

特性 数据卷(Volumes) 绑定挂载(Bind mounts)
管理方式 由 Docker 完全管理,存储在 Docker 主机上的特定目录(Linux 中通常是 /var/lib/docker/volumes/)。 由你(用户)管理,可以是宿主机上的任何路径。Docker 只是“借用”这个路径给容器。
依赖宿主机的目录结构 不依赖,数据卷的存储位置对用户透明。 强依赖,必须指定宿主机的绝对路径,且该路径必须存在(或者用 -v 时 Docker 自动创建,但用 --mount 则必须存在)。
数据持久性和生命周期 独立于容器,即使删除所有使用该卷的容器,卷依然存在,除非你显式删除。适合存储重要数据。 与宿主机目录绑定,目录存在则数据存在;如果删除宿主机目录,数据即丢失。
共享能力 可以在多个容器之间轻松共享,即使这些容器运行在不同的宿主机上(借助网络卷驱动)。 只能在同一个宿主机上的容器之间共享,因为它们必须指向相同的宿主机路径。
备份和迁移 易于备份、恢复和迁移,因为 Docker 提供了专门的卷管理命令(如 docker volume create、备份时可以打包 /var/lib/docker/volumes/ 下的内容)。 备份需要直接操作宿主机上的目录,可移植性较差(路径可能在不同机器上不一致)。
权限和安全性 由 Docker 管理,非 Docker 进程不应该直接访问,因此更安全。 容器内的进程可以访问宿主机上的任何文件(取决于挂载目录的权限),如果挂载敏感目录(如 /etc/var),存在安全风险。
性能 由于 Docker 直接管理,在某些场景下(特别是 Mac 和 Windows 上的 Docker Desktop)性能优于绑定挂载。 在 Linux 上性能接近原生,但在 Docker Desktop(使用虚拟机)上,如果大量文件读写,可能比数据卷慢。
适用场景 - 生产环境持久化数据(如数据库文件) - 在多个容器间共享数据 - 需要远程存储或云存储(通过卷驱动) - 不希望暴露宿主机目录结构 - 开发环境:热加载代码(修改宿主机文件,容器内即时生效) - 将宿主机的配置文件传入容器 - 需要访问宿主机特定设备或文件(如 /dev

如何选择

  • 生产环境,尤其是数据库、缓存等有状态服务:首选数据卷。它们更安全、更可移植,且 Docker 会负责管理存储细节。

  • 开发环境,需要实时同步代码:首选绑定挂载。你可以直接在 IDE 中修改代码,容器内的应用会自动感知变化(如果应用支持热重载)。

  • 在容器之间共享数据:如果容器在同一宿主机,绑定挂载也能用;但如果需要跨主机共享,则必须使用数据卷(配合网络存储驱动)。

  • 配置注入:两者都可以,但绑定挂载更直接(如挂载一个配置文件)。


Dockerfile

概念

Dockerfile是一个创建镜像所有命令的文本文件,包含了一条条指令和说明, 每条指令构建一层,,通过docker build命令,根据Dockerfile的内容构建镜像,因此每一条指令的内容, 就是描述该层如何构建。有了Dockefile,,就可以制定自己的docker镜像规则,只需要在Dockerfile上添加或者修改指令,就可生成docker 镜像。


Dockerfile构建过程

dockerfile的关键字建议使用大写,它是从上往下按照循序执行的,在dockerfile中,#代表注释。我们可以通过这个脚本来生成镜像,脚本中的每一个命令,都是一层镜像。

dockerfile是面向开发的,发布项目做镜像的时候就要编写dockerfile文件。
dockerfile:构建文件,定义了一切的步骤,源代码。
dockerImanges:通过dockerfile构建生成的镜像,最终发布和运行的产品。
docker容器:容器就是镜像运行起来提供服务的。


Dockerfile指令选项

Dockerfile 指令选项:
 
FROM                  #基础镜像 。 (centos)
MAINTAINER            #镜像的作者和邮箱。(已被弃用,结尾介绍代替词)
RUN                   #镜像构建的时候需要执行的命令。
CMD                   #类似于 RUN 指令,用于运行程序(只有最后一个会生效,可被替代)
EXPOSE                #对外开放的端口。
ENV                   #设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。
ADD                   # 步骤:tomcat镜像,这个tomcat压缩包。添加内容。
COPY                  #复制指令,将文件拷贝到镜像中。
VOLUME                #设置卷,挂载的主机目录。
USER                  #用于指定执行后续命令的用户和用户组,
                       这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。
WORKDIR               #工作目录(类似CD命令)。
ENTRYPOINT            #类似于 CMD 指令,但其不会被 docker run 
                       的命令行参数指定的指令所覆盖,会追加命令。
ONBUILD               #当构建一个被继承Dokcerfile,就会运行ONBUILD的指令。出发执行。
 
 
注意:CMD类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:
CMD 在docker run 时运行。
RUN 是在 docker build。
作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。
CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。
如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。
 
LABEL(MAINTALNER已经被弃用了,目前是使用LABEL代替)
LABEL 指令用来给镜像添加一些元数据(metadata),以键值对的形式,语法格式如下:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
比如我们可以添加镜像的作者:
LABEL org.opencontainers.image.authors="runoob"

实战测试

创建一个简单的 Node.js 应用,并用 Docker 容器化它。

  1. 创建项目目录和应用文件
mkdir my-node-app
cd my-node-app
  1. 创建 package.json
{
  "name": "my-node-app",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}
  1. 创建 index.js
const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('Hello from Docker!');
});

app.listen(port, () => {
  console.log(`App listening at http://localhost:${port}`);
});
  1. 创建 Dockerfile
# 使用官方 Node.js 镜像作为基础
FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 复制 package.json 和 package-lock.json(如果有)
COPY package*.json ./

# 安装依赖
RUN npm install

# 复制应用代码
COPY . .

# 暴露应用端口
EXPOSE 3000

# 启动应用
CMD ["npm", "start"]
  1. 构建镜像
docker build -t my-node-app .
  1. 运行容器
docker run -d -p 3000:3000 --name my-app my-node-app
  1. 测试应用
    访问 http://localhost:3000,你应该看到“Hello from Docker!”。
  2. 停止并清理
docker stop my-app
docker rm my-app

进阶之Docker Compose

在实际项目中,应用往往由多个服务组成(比如前端、后端、数据库)。手动管理每个容器会很繁琐,这时我们可以使用 Docker Compose。Docker Compose 允许你通过一个 YAML 文件定义所有服务,然后用一条命令启动整个应用。

下面是一个简单的例子,包含一个 Node.js 应用和一个 Redis 缓存:

# docker-compose.yml
version: '3'
services:
  web:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - redis
    volumes:
      - .:/app          # 绑定挂载,便于开发热重载
  redis:
    image: "redis:alpine"
    volumes:
      - redis-data:/data  # 使用命名卷持久化 Redis 数据

volumes:
  redis-data:  # 声明命名卷

执行如下命令以启动整个应用栈。

docker-compose up -d

补充:–volume和–mount

简单来说,-v(或 --volume)是更古老、更简洁的语法,它将所有配置选项糅合在一个字符串里。而 --mount 是更新、更详细的语法,它将配置选项分开,可读性更强,也更明确。

特性 --volume (或 -v) --mount 说明
语法风格 将选项合并到一个字段中,用冒号(:)分隔。例如: -v /host/path:/container/path:ro 使用多个键值对(<key>=<value>),用逗号分隔。例如: --mount type=bind,source=/host/path,target=/container/path,readonly --mount 的语法更显式,通过 key=value 的形式让你清楚地知道每个参数的含义。
路径处理 如果使用绑定挂载(-v /host/path:/container/path),宿主机上的源路径 (/host/path) 不存在,Docker 会自动为你创建一个空目录 如果使用绑定挂载(type=bind),宿主机上的源路径 (source=...) 不存在,Docker 会抛出一个错误,不会自动创建。 -v 的自动创建行为在简化命令的同时,也可能掩盖了路径配置错误的问题。--mount 则更加严谨,有助于你及早发现问题。
功能支持 支持基本的卷(volume)和绑定挂载(bind mount)功能,如设置只读(:ro)。 支持所有功能,包括 -v 的所有功能。此外,在需要指定卷驱动程序选项(volume driver options)或配置复杂的挂载选项时,必须使用 --mount --mount 是为更复杂、更高级的用例设计的,它提供了更细粒度的控制。
在服务中使用 不支持docker service create 命令中使用。 支持docker service create 命令中用于定义服务的挂载。 如果你在使用 Docker Swarm 编排容器,那么 --mount 是你唯一的选择。
推荐程度 对于简单的场景,特别是快速实验,使用 -v 很便捷。 Docker 官方推荐新用户和在生产环境中使用 --mount,因为它更明确、更强大,可以避免因语法混淆导致的问题。 在实际项目中,尤其是在编写需要长期维护的脚本或配置文件时,建议优先考虑 --mount

使用建议

  1. 日常开发/简单测试:使用 -v 可以快速键入命令,例如:
docker run -d -v my-data:/app/data my-app
  1. 生产环境/复杂脚本:建议使用 --mount,以提高配置的清晰度和可靠性。上面的命令如果用 --mount 写,会是:
docker run -d \
  --mount source=my-data,target=/app/data \
  my-app

参考:
docker的使用
Docker Composes使用

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐