Docker 是什么

你可以把 Docker 理解为软件世界的集装箱。就如 Docker logo 所展示的,是一头鲸鱼驮着若干集装箱。Docker 可以允许开发者将应用以及所有依赖项(库、环境等)打包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 或 Windows 机器上。
诞生的背景
应用在开发、测试、生产环境中,由于环境依赖、配置差异等等问题,常导致 "在我这能跑" 的难题。传统虚拟机虽能隔离环境,但资源占用高、启动慢、笨重,无法满足轻量、快速部署的需求。 Docker 便在这种背景下诞生。
Docker 与虚拟机的区别
虚拟机能实现环境隔离,解决部署环境一致性的问题。那么 Docker 又是怎么解决的,有什么优势呢?
一言以蔽之:虚拟机虚拟化硬件,依赖完整的操作系统;而容器虚拟化操作系统,共享主机内核,因此更轻量、更快速。
通俗化的理解就是:
-
虚拟机就像一栋独栋别墅。拥有自己的地基、墙体、水电系统(虚拟硬件)和完整的家庭空间(操作系统)。私密性好(隔离强),但建造和启动慢,占用土地多(资源消耗大)。
-
Docker 容器就像一栋公寓楼里的一个单间。所有房间共享大楼的地基和主体结构(主机内核),但每个房间有自己独立的墙壁(隔离)、家具(应用与依赖)。启动快(拎包入住),非常节省资源。
核心概念
Dockerfile
文本文件,描述如何构建镜像(例如指定基础镜像、安装软件、复制文件等)。
镜像 Image
Dockerfile 构建后的产物即镜像(Image)。镜像是只读的,类似于安装系统用的 .iso 文件,它包含了运行应用所需的所有东西(代码、库、环境变量、配置文件)。
层 Layer
镜像由层构成,每个层表示文件系统的变更------增加、修改、删除。
容器 Container
容器就是实际运行中的实例,基于镜像创建而来,与其他容器隔离。类似于一个轻量级的、隔离的虚拟机(但本质是进程)。
仓库 Registry
存储和分发镜像的平台,如 Docker Hub(Docker 官方平台)。
Docker Compose
容器的一个最佳实践是每个容器应该只做一件事。但实际项目可能需要同时运行过个容器,并且同时管理容器间的网络连接等。使用 Docker Compose,可以实现在一个 YAML 文件中定义所有容器及其配置。然后使用一条命令便可启动运行。
例如一个项目包含前端、后端、数据库,分别对应三个镜像,这一个项目可以用一个 docker-compose.yml 文件描述,包含三个镜像,用一条命令就能启动。
Docker 架构

Docker 采用客户端-服务器架构。Docker 客户端和守护进程可以运行在同一系统上,也可以将 Docker 客户端连接到远程 Docker 守护进程。Docker 客户端和守护进程通过 REST API 进行通信,通信方式可以是 UNIX 套接字或网络接口。
-
Docker 守护进程(
dockerd)监听 Docker API 请求,并管理 Docker 对象,如镜像、容器、网络和卷。守护进程还可以与其他守护进程通信,以管理 Docker 服务。 -
Docker 客户端(
docker)是许多 Docker 用户与 Docker 交互的主要方式。当你使用docker run等命令时,客户端会将这些命令发送给dockerd,由其执行。docker命令使用 Docker API。Docker 客户端可以与多个守护进程通信。另一个 Docker 客户端是 Docker Compose,它允许你操作由一组容器组成的应用程序。
Docker 的基础使用场景
用 Dockerfile 构建镜像
以下是一个简单示例,描述了一个前端项目的 Dockfile
dockerfile
# 指定基础镜像,使用官方 nginx 镜像
FROM nginx:alpine
# 删除默认 nginx 静态资源
RUN rm -rf /usr/share/nginx/html/*
# 拷贝 web 项目的 build 文件到 nginx 静态目录下
COPY dist/ /usr/share/nginx/html/
# 拷贝自定义 nginx 配置
COPY default.conf /etc/nginx/conf.d/default.conf
# 暴露端口 80
EXPOSE 80
# 让 NGINX 在前台运行,防止 Docker 容器启动后立即退出
CMD [ "nginx" , "-g" , "daemon off;" ]
如上 FROM/RUN/CMD 等是 Dockerfile 的指令
| 指令 | 说明 |
|---|---|
| FROM | 定制的镜像都是基于某个基础镜像,FROM 就是用来指定基础镜像 |
| RUN | 在构建过程中的执行命令 |
| CMD | 创建容器时要执行的默认命令 |
| COPY | 复制指令,从上下文目录中复制文件或者目录到容器内指定路径 |
| EXPOSE | 声明容器运行时监听的网络端口 |
| ENV | 在容器内部设置环境变量 |
构建指令
sh
# 以当前目录为上下文构建名称为 frontend 的镜像,即当前目录需要有 Dockerfile
docker build -t frontend .
frontend 为镜像名称,也可以指定标签如 frontend:v1,frontend:latest,
镜像推送和拉取
如果是为了在其他机器上使用镜像,那我们需要用到镜像仓库。
需要登陆才可使用进行推送操作
sh# docker 登陆命令 docker login [hub URL]
将本地镜像推送到远程的镜像仓库
sh
docker push my-image:latest
从镜像仓库拉取指定镜像
sh
docker pull my-image:latest
关于拉取镜像:拉取的镜像会存在本地,然后当基于镜像创建容器时,如果指定的镜像本地没有,则会默认从仓库拉取镜像,有则使用本地镜像。
本地可以安装使用 Docker Desktop,查看镜像、容器等,非常方便。
基于镜像创建容器
创建容器使用 docker run 命令,如启动一个基于 ubuntu 的镜像,启动后在前台运行(即命令行不会退出)
sh
docker run ubuntu
一般有几种场景:
后台运行容器
在后台运行 ubuntu 容器并返回容器 ID
sh
docker run -d ubuntu
交互式运行并分配终端
以交互模式运行 ubuntu 容器,并启动一个 Bash shell
sh
docker run -it ubuntu /bin/bash
临时容器
启动一个 ubuntu 容器,执行 xxxx 命令,完成后自动删除容器
sh
docker run --rm ubuntu sh -c "xxxxx"
另外一下是一些常用的参数:
| 参数 | 示例 | 含义 |
|---|---|---|
| --name | --name my_container | 指定容器名称,不指定的话 docker 会随机生成一个名称 |
| -p | -p 8080:80 | 端口映射,主机的 8080 端口映射为容器内的 80 端口 |
| -v | -v /host/data:/container/data | 挂载卷,将主机的 /host/data 目录挂载到容器内的 /container/data 目录 |
| -e, --env | -e MY_ENV=my_value | 设置环境变量 MY_ENV=my_value |
| --network | --network host | 指定网络模式 |
容器管理
容器启动、停止、重启
sh
docker start my_container
docker stop my_container
docker restart my_container
删除一个或多个已经停止容器
sh
docker rm my_container
在运行中的容器中执行命令
sh
# 在 my_container 容器中执行 ls /app 命令
docker exec my_container ls /app
查看当前运行的容器
sh
# 查看所有运行的容器
docker ps
# 查看所有容器,包含停止的
docker ps -a
# 显示所有容器 ID
docker ps -aq
# 输出示例
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
09b4274e4356 project__frontend "/docker-entrypoint...." 3 hours ago Up 3 hours 0.0.0.0:8081->80/tcp, [::]:8081->80/tcp frontend
30486b3d96d0 project__backend "./wait-for-mysql.sh" 3 hours ago Up 3 hours 1024/tcp backend
929f7bfcf316 project__mysql "docker-entrypoint.s..." 3 hours ago Up 3 hours 3306/tcp, 33060/tcp mysql
6d78b99b0e3b redis:alpine "docker-entrypoint.s..." 3 hours ago Up 3 hours 6379/tcp redis
docker ps 常用来查看所有容器的运行状态。容器状态有 7 种:
-
created,已创建,从未启动过的容器 -
running,运行中,通过docker start或docker run启动。 -
paused,已暂停,通过docker pause暂停。 -
restarting,重启中,由于容器指定的重启策略而正在启动。 -
exited,已停止,一个不再运行的容器。例如,容器内的进程已完成,或者容器已通过docker stop命令停止。 -
removing,删除中,执行docker rm,容器正在被移除 -
dead,已失效,例如,由于外部进程占用资源而仅被部分移除的容器。dead的容器无法(重新)启动,只能被移除
查看容器运行的日志
sh
docker logs my_container
# 跟随日志,类似于 tail -f
docker logs -f my_container
查看容器端口映射信息
sh
docker port my_container
# 输出示例
80/tcp -> 0.0.0.0:8081 # ipv4
80/tcp -> [::]:8081 # ipv6
Docker Compose 的使用场景
实际一个项目,可能会有多个服务组成,即完整运行一个服务可能会基于多个镜像运行多个容器,包含若干配置,以及多个容器协作需要创建网络等。docker compose 就是针对这种情况的解决方案,通过 docker-compose.yml 文件可以配置多个容器组成的服务,以及它们依赖的网络配置等。然后可以用一个命令就能启动这个多容器的项目。
docker-compose.yml
如下所示,docker-compose.yml 定义了 5 个服务,包含数据库服务、后端服务、前端服务
yaml
services: # 服务配置
mysql:
container_name: mysql # 容器名称
build: ./mysql # 构建目录
platform: linux/amd64 # 架构类型
image: project__mysql # 镜像名称(本地构建可不需要)
restart: always # 重启策略
volumes: # 依赖卷
- mysql-data:/var/lib/mysql
redis:
container_name: redis
image: redis:alpine
restart: always
volumes:
- redis-data:/data
backend:
container_name: backend
build: ./backend
platform: linux/amd64
image: project__backend
depends_on: # 启动顺序依赖
mysql:
condition: service_healthy
redis:
condition: service_started
frontend:
container_name: frontend
build: ./frontend
platform: linux/amd64
image: project__frontend
ports: # 端口映射
- "8081:80"
depends_on:
- backend
frontend-admin:
container_name: frontend_admin
build: ./frontend-admin
platform: linux/amd64
image: project__frontend-admin
ports:
- "8082:80"
depends_on:
- backend
volumes: # 卷配置
mysql-data:
redis-data:
docker-compose.yml 所处目录的上下文示意:
scss
.
|-mysql
| |-Dockerfile
| `-(other files)
|-backend
| |-Dockerfile
| `-(other files)
|-frontend
| |-Dockerfile
| `-(other files)
|-frontend-admin
| |-Dockerfile
| `-(other files)
`-docker-compose.yml
构建与推送镜像
构建
含有 build 配置的服务,会自动依赖其所指定的目录进行构建,指定目录下需包含 Dockerfile
sh
docker compose build
推送
构建完成后,可以将构建完成的镜像自动推送到镜像仓库,即含有 build 配置的项会自动推送到 image 定义的镜像位置(当然镜像仓库是有权限控制的)
sh
docker compose push
启动命令与过程
启动命令
sh
# 启动服务在前台运行,一旦退出则服务停止
docker compose up
# 启动服务在后台运行
docker compose up -d
启动的具体过程
-
前期准备与验证
- 读取 Compose 文件
- 验证配置,检查 YAML 语法、服务定义、网络、卷等配置是否正确。
- 项目环境,确定项目名称(默认是当前目录名,可通过
-p指定)。
-
构建与拉取镜像
- 如果配置了
build,会构建镜像,如果本地构建过,则直接使用缓存,否则会进行构建,如果配置了--build则会使用进行构建。 - 如果配置了
image,会拉取镜像,如果本地不存在就会从镜像仓库拉取,如果配置了--pull=always会始终从镜像仓库拉取。
- 如果配置了
-
创建网络和卷
- 创建网络:根据
networks部分创建自定义网络。默认会为整个项目创建一个名为{project-name}_default的网络,所有服务都会连接到这个网络,以便通过服务名进行通信。 - 创建卷:根据
volumes部分创建命名的持久化卷。如果卷已存在,则直接使用。
- 创建网络:根据
-
启动服务(容器)
-
按依赖顺序启动,会根据
depends_on配置来确定启动顺序。- 例如,一个
web服务依赖db服务,那么 Compose 会先启动db,然后再启动web。但注意depends_on只能决定启动顺序,并不能保证web启动时db已经正常运行
- 例如,一个
-
容器创建:为每个服务创建并启动容器,这相当于对每个服务执行
docker run -
重启策略:会根据
restart策略来管理容器的生命周期
-
-
服务健康检查(如果配置了
healthcheck)-
等待健康状态:如果服务配置了健康检查(
healthcheck),Compose 会等待容器变为 healthy 状态,然后再启动依赖于它的下一个服务。 -
与
depends_on的区别:depends_on只控制启动顺序(容器进程运行)。healthcheck+condition: service_healthy控制就绪顺序(容器内应用真正准备好接收请求)。这是更可靠的方式
-
-
后续
-
前台模式:会用一个彩色的输出流输出所有服务的日志,按下
Ctrl+C会停止所有正在运行的服务 -
后台模式(
-d参数):启动命令会立即返回,只打印出创建了哪些容器。之后可以用docker compose logs来查看日志,使用docker compose ps查看状态。
-
使用启动命令时常用的一些参数
| 参数 | 示例 | 含义 |
|---|---|---|
| -f | docker compose -f xxx.yml up | 用于指定某个 docker compose yml 配置启动,默认是当前目录下的 docker-compose.yml 文件 |
| -p | docker compose -p app up | 指定启动项目名称,默认是以当前的目录名 |
| --build | docker compose up --build | 启动时强制重新构建镜像,即使镜像已存在 |
| --pull | docker compose up --pull= | 指定启动前拉镜像的策略, - always,总是从远程拉镜像,即使本地已经有了,保证运行时是最新镜像 - missing,(默认值)如果本地不存在指定的镜像,则从仓库拉取。如果本地已存在,则直接使用本地镜像 - never,从不拉取镜像。如果本地不存在该镜像,则命令失败 |
运维:查看、停止
查看日志
sh
docker compose logs
停止并删除 docker compose 所创建的容器
sh
docker compose down
重启 docker compose 所创建的容器
sh
docker compose restart
网络 Network
网络模式
安装 Docker 时,它会自动创建三个网络,bridge(创建容器默认连接到此网络)、 none 、host。
运行容器时,可以指定容器运行的网络模式。
-
Host
- 通过
--network=host指定 - 容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口
- 通过
-
Bridge
- 通过
--network=bridge指定,如果不指定,会默认以 brdge 模式 - 为每一个容器分配、设置IP等,并将容器连接到一个 docker0 虚拟网桥,通过 docker0 网桥以及 Iptables nat 表配置与宿主机通信
- 通过
-
None
- 通过
--network=none指定 - 关闭容器的网络功能
- 通过
-
Container
- 通过
--network=container:NAME_or_ID指定 - 创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围。
- 通过
-
指定自定义网络
相关命令
sh
# 列出所有网络
docker network ls
# 创建一个新的网络
docker network create <network>
# 删除指定的网络
docker network rm <network>
# 连接容器到网络
docker network connect <network> <container>
# 断开容器与网络的连接
docker network disconnect <network> <container>
卷 Volume
卷是容器的持久性数据存储,由 Docker 创建和管理。可以使用docker volume create命令显式创建卷,Docker 也可以在创建容器或服务时创建卷。
创建卷时,它会存储在 Docker 主机上的一个目录中。将卷挂载到容器时,挂载到容器中的就是这个目录。这与绑定挂载的工作方式类似,不同之处在于卷由 Docker 管理,并且与主机的核心功能隔离开来。
卷相关的命令
sh
# 列出所有卷
docker volume ls
# 创建一个新的卷
docker volume create <volume>
# 删除指定的卷
docker volume rm <volume>
# 显示卷的详细信息
docker volume inspect <volume>
Docker in Docker (DinD)
Docker in Docker(简称 DinD)指的是在一个 Docker 容器内部运行一个完整的 Docker 守护进程(Docker Daemon)和客户端(Docker CLI)。
简单来说,就是"容器里的容器"。你可以在一个容器中执行 docker build、docker run 等命令,就像在宿主机上一样。
可以使用官方的 docker:dind 镜像进行构建。这个镜像经过特殊配置,可以在容器内启动并运行 Docker 守护进程。运行此类容器通常需要 --privileged(特权)模式。
优点
-
环境隔离:构建环境与宿主机完全隔离,非常干净,不会相互污染。
-
安全性相对较高:与 DoD 相比,它对宿主机的影响更小,进程和文件系统都是隔离的。
缺点
-
性能开销大:在容器内运行完整的 Docker 守护进程和存储驱动(通常使用效率较低的
vfs),资源占用高。 -
无法共享缓存:每次启动一个 DinD 容器,都是一个全新的环境,之前构建的镜像缓存会丢失,导致构建速度慢。
业务场景
业务中需要通过 docker compose 启动多个容器,而部署环境的容器中使用了 host 的网络模式,内部再使用 docker compose 启动会默认创建网络,由于要部署的环境众多,这会导致 host 网络无法分配的问题。此时需要使用 Docker in Docker 的方式进行部署,隔绝项目网络与部署环境的网络。
结语
以上是业务中使用到的 Docker 知识的简单总结,本文对 Docker 相关知识的多个方面均有涉及,但受限于篇幅和定位,各部分内容的探讨尚属浅显。若希望深入理解 Docker 的底层原理、高级应用及最佳实践,可进一步阅读专业技术文档如 Docker 官方网站等,以获取更系统、全面的知识体系。