文章目录
- [第 1 章 容器生态系统](#第 1 章 容器生态系统)
-
- [1.1 了解容器技术](#1.1 了解容器技术)
- [1.2 容器生态系统](#1.2 容器生态系统)
- [1.3 容器生态系统 (续)](#1.3 容器生态系统 (续))
- [1.4 安装 docker](#1.4 安装 docker)
-
- 环境选择
- 安装操作系统
-
- [配置静态 IP 和主机名](#配置静态 IP 和主机名)
- [安装 Docker](#安装 Docker)
-
- 卸载旧版本 (可选)
- 安装必要工具
- [allinone 部署 - 安装软件](#allinone 部署 - 安装软件)
- 配置服务
- 验证安装
- 配置镜像加速器 (华为云)
- 运行第一个容器
- 运行第二个容器
- [1.5 docker C/S 分离部署](#1.5 docker C/S 分离部署)
-
- [docker server 端配置](#docker server 端配置)
- [docker client 端配置](#docker client 端配置)
- [第 2 章 容器架构](#第 2 章 容器架构)
-
- [2.1 容器 What,Why,How](#2.1 容器 What,Why,How)
- [2.2 Docker 介绍](#2.2 Docker 介绍)
-
- [Docker 版本](#Docker 版本)
- [Docker 核心概念](#Docker 核心概念)
-
- [Docker 镜像](#Docker 镜像)
- [Docker 容器](#Docker 容器)
- [Docker 仓库](#Docker 仓库)
- [2.3 Docker 架构详解](#2.3 Docker 架构详解)
- [2.4 Docker 组件如何协作?](#2.4 Docker 组件如何协作?)
- [第 3 章 镜像](#第 3 章 镜像)
-
- [3.1 最小的镜像](#3.1 最小的镜像)
-
- 镜像的内部结构
-
- [hello-world - 最小的镜像](#hello-world - 最小的镜像)
- [3.2 base 镜像](#3.2 base 镜像)
- [3.3 镜像的分层结构](#3.3 镜像的分层结构)
-
- [为什么 Docker 镜像要采用这种分层结构呢?](#为什么 Docker 镜像要采用这种分层结构呢?)
- 可写的容器层
- [3.4 构建镜像](#3.4 构建镜像)
-
- [Docker 容器文件系统](#Docker 容器文件系统)
- [Docker 提供了两种构建镜像的方法](#Docker 提供了两种构建镜像的方法)
- [docker commit](#docker commit)
-
- 第一步,运行容器
- [第二步,安装 vim](#第二步,安装 vim)
- 第三步,保存为新镜像
- [3.5 Dockerfile 构建镜像](#3.5 Dockerfile 构建镜像)
- [3.6 镜像的缓存特性](#3.6 镜像的缓存特性)
- [3.7 调试 Dockerfile](#3.7 调试 Dockerfile)
- [3.8 Dockerfile 常用指令](#3.8 Dockerfile 常用指令)
- [3.9 RUN vs CMD vs ENTRYPOINT](#3.9 RUN vs CMD vs ENTRYPOINT)
-
- [Shell 和 Exec 格式](#Shell 和 Exec 格式)
-
- [Shell 格式](#Shell 格式)
- [Exec 格式](#Exec 格式)
- RUN
- CMD
- ENTRYPOINT
-
- [Exec 格式](#Exec 格式)
- [Shell 格式](#Shell 格式)
- 最佳实践
- [Dockerfile 案例:配置 SSH 镜像](#Dockerfile 案例:配置 SSH 镜像)
- [Dockerfile 案例:自定义 httpd 镜像](#Dockerfile 案例:自定义 httpd 镜像)
- [Dockerfile 案例:自定义 mycentosjava8](#Dockerfile 案例:自定义 mycentosjava8)
- [3.10 镜像命名的最佳实践](#3.10 镜像命名的最佳实践)
-
- 为镜像命名
- [小心 latest tag](#小心 latest tag)
- [tag 使用最佳实践](#tag 使用最佳实践)
- [3.11 使用公共 Registry-dockerhub](#3.11 使用公共 Registry-dockerhub)
-
- [步骤 1:注册 Docker Hub 账号](#步骤 1:注册 Docker Hub 账号)
- [步骤 2:在 Docker Host 上登录](#步骤 2:在 Docker Host 上登录)
- [步骤 3:修改镜像的 repository 使之与 Docker Hub 账号匹配](#步骤 3:修改镜像的 repository 使之与 Docker Hub 账号匹配)
- [步骤 4:通过 docker push 将镜像上传到 Docker Hub](#步骤 4:通过 docker push 将镜像上传到 Docker Hub)
- [步骤 5:查看上传的镜像](#步骤 5:查看上传的镜像)
- [步骤 6:下载镜像](#步骤 6:下载镜像)
- [使用公共 Registry - 华为云](#使用公共 Registry - 华为云)
-
- [步骤 1:登录华为云](#步骤 1:登录华为云)
- [步骤 2:进入容器镜像服务 SWR](#步骤 2:进入容器镜像服务 SWR)
- [步骤 3:获取登录指令](#步骤 3:获取登录指令)
- [步骤 4:在 Docker Host 上登录华为云 SWR](#步骤 4:在 Docker Host 上登录华为云 SWR)
- [步骤 5:上传镜像](#步骤 5:上传镜像)
- [步骤 6:查看上传的镜像](#步骤 6:查看上传的镜像)
- [步骤 7:下载镜像](#步骤 7:下载镜像)
- [3.12 搭建本地 Registry](#3.12 搭建本地 Registry)
- [3.13 Docker 镜像小结](#3.13 Docker 镜像小结)
- 将httpd镜像保存为httpd.tar
- 将httpd和hello-world镜像保存为images.tar
- 导入镜像
第 1 章 容器生态系统
1.1 了解容器技术
- 系统讲解当前最流行的容器技术。从容器的整个生态环境到各个具体的技术,从整体到细节逐一讨论。
- 重实践并兼顾理论。从实际操作的角度带领大家学习容器技术。
为什么要写这个?
简单回答是:容器技术非常热门,但门槛高。
容器技术是继大数据和云计算之后又一炙手可热的技术,而且未来相当一段时间内都会非常流行。对 IT 行业来说,这是一项非常有价值的技术。而对 IT 从业者来说,掌握容器技术是市场的需要,也是提升自我价值的重要途径。
现在以 Docker 为代表的容器技术来了,而且关注度越来越高,这一点可以从 google 趋势中 Docker 的搜索上升趋势 (蓝色曲线) 中清楚看到。

1.2 容器生态系统
对于像容器这类平台级别的技术,通常涉及的知识范围会很广,相关的软件、解决方案也会很多,初学者往往容易迷失。
那怎么办呢?
我们可以从生活经验中寻找答案。
当我们去陌生城市旅游想了解一下这个城市一般我们会怎么做?
我想大部分人应该会打开手机看一下这个城市的地图:
- 城市大概的位置和地理形状是什么?
- 都由哪几个区或县组成?
- 主要的交通干道是哪几条?
同样的道理,学习容器技术我们可以先从天上鸟瞰一下:
- 容器生态系统包含哪些不同层次的技术?
- 不同技术之间是什么关系?
- 哪些是核心技术哪些是辅助技术?
首先得对容器技术有个整体认识,之后我们的学习才能够有的放矢,才能够分清轻重缓急,做到心中有数,这样就不容易迷失了。
接下来我会根据自己的经验帮大家规划一条学习路线,一起探索容器生态系统。
学习新技术得到及时反馈是非常重要的,所以我们马上会搭建实验环境,并运行第一个容器,感受什么是容器。
千里之行始于足下,让我们从了解生态系统开始吧。
鸟瞰容器生态系统
一谈到容器,大家都会想到 Docker。Docker 现在几乎是容器的代名词。确实,是 Docker 将容器技术发扬光大。同时,大家也需要知道围绕 Docker 还有一个生态系统。Docker 是这个生态系统的基石,但完善的生态系统才是保障 Docker 以及容器技术能够真正健康发展的决定因素。
大致来看,容器生态系统包含核心技术、平台技术和支持技术。

下面分别介绍。
容器核心技术
容器核心技术是指能够让 container 在 host os 上运行起来的那些技术。

容器核心技术包括容器规范、容器 runtime、容器管理工具、容器定义工具、Registry 以及容器 OS,下面分别介绍。
容器规范
容器不光是 Docker,还有其他容器,比如 CoreOS 的 rkt、红帽公司 podman、containerd。为了保证容器生态的健康发展,保证不同容器之间能够兼容,包含 Docker、CoreOS、Google 在内的若干公司共同成立了一个叫 Open Container Initiative (OCI) 的组织,其目的是制定开放的容器规范。

目前 OCI 发布了两个规范:runtime spec 和 image format spec。
有了这两个规范,不同组织和厂商开发的容器能够在不同的 runtime 上运行。这样就保证了容器的可移植性和互操作性。
容器 runtime
runtime 是容器真正运行的地方。runtime 需要跟操作系统 kernel 紧密协作,为容器提供运行环境。
如果大家用过 Java,可以这样来理解 runtime 与容器的关系:Java 程序就好比是容器,JVM 则好比是 runtime。JVM 为 Java 程序提供运行环境。同样的道理,容器只有在 runtime 中才能运行。

lxc、runc 和 rkt 是目前主流的三种容器 runtime。
- lxc 是 Linux 上老牌的容器 runtime。Docker 最初也是用 lxc 作为 runtime。
- runc 是 Docker 自己开发的容器 runtime,符合 oci 规范,也是现在 Docker 的默认 runtime。
- rkt 是 CoreOS 开发的容器 runtime,符合 oci 规范,因而能够运行 Docker 的容器。
容器管理工具
光有 runtime 还不够,用户得有工具来管理容器。容器管理工具对内与 runtime 交互,对外为用户提供 interface,比如 CLI。这就好比除了 JVM,还得提供 java 命令让用户能够启停应用。

容器管理工具包括:
- lxd:lxc 对应的管理工具。
- docker engine:runc 的管理工具,包含后台 deamon 和 cli 两个部分。我们通常提到 Docker,一般就是指的 docker engine。
- rkt cli:rkt 的管理工具。
容器定义工具
容器定义工具允许用户定义容器的内容和属性,这样容器就能够被保存、共享和重建。

- docker image:docker 容器的模板,runtime 依据 docker image 创建容器。
- dockerfile:包含若干命令的文本文件,可以通过这些命令创建出 docker image。
- ACI (App Container Image):与 docker image 类似,是 CoreOS 开发的 rkt 容器的 image 格式。
Registry
容器是通过 image 创建的,需要有一个仓库来统一存放 image,这个仓库就叫做 Registry。

Registries 包括:
- Docker Registry:企业可以用其构建私有的 Registry。
- Docker Hub(https://hub.docker.com):Docker 为公众提供的托管 Registry,上面有很多现成的 image,为 Docker 用户提供了极大的便利。
- Quay.io(https://quay.io/):另一个公共托管 Registry,提供与 Docker Hub 类似的服务。
容器 OS
由于有容器 runtime,几乎所有的 Linux、MAC OS 和 Windows 都可以运行容器。但这并没有妨碍容器 OS 的问世。

容器 OS 是专门运行容器的操作系统。与常规 OS 相比,容器 OS 通常体积更小,启动更快。因为是为容器定制的 OS,通常它们运行容器的效率会更高。
目前已经存在不少容器 OS,CoreOS、atomic 和 ubuntu core 是其中的杰出代表。
下一节继续介绍容器平台技术和容器支持技术。
1.3 容器生态系统 (续)
容器生态系统包含核心技术、平台技术和支持技术三个方面。上一节我们讨论了核心技术,本节讨论另外两个部分。
容器平台技术
容器核心技术使得容器能够在单个 host 上运行。而容器平台技术能够让容器作为集群在分布式环境中运行。

容器平台技术包括容器编排引擎、容器管理平台和基于容器的 PaaS。
容器编排引擎
基于容器的应用一般会采用微服务架构。在这种架构下,应用被划分为不同的组件,并以服务的形式运行在各自的容器中,通过 API 对外提供服务。为了保证应用的高可用,每个组件都可能会运行多个相同的容器。这些容器会组成集群,集群中的容器会根据业务需要被动态地创建、迁移和销毁。
大家可以看到,这样一个基于微服务架构的应用系统实际上是一个动态的可伸缩的系统。这对我们的部署环境提出了新的要求,我们需要有一种高效的方法来管理容器集群。而这,就是容器编排引擎要干的工作。
所谓编排 (orchestration),通常包括容器管理、调度、集群定义和服务发现等。通过容器编排引擎,容器被有机的组合成微服务应用,实现业务需求。

docker swarm、kubernetes、mesos+marathon 是当前主流的容器编排引擎。
- docker swarm 是 Docker 开发的容器编排引擎。
- kubernetes 是 Google 领导开发的开源容器编排引擎,同时支持 Docker 和 CoreOS 容器。
- mesos 是一个通用的集群资源调度平台,mesos 与 marathon 一起提供容器编排引擎功能。
容器管理平台
容器管理平台是架构在容器编排引擎之上的一个更为通用的平台。通常容器管理平台能够支持多种编排引擎,抽象了编排引擎的底层实现细节,为用户提供更方便的功能,比如 application catalog 和一键应用部署等。

Rancher 和 ContainerShip 是容器管理平台的典型代表。
基于容器的 PaaS
基于容器的 PaaS 为微服务应用开发人员和公司提供了开发、部署和管理应用的平台,使用户不必关心底层基础设施而专注于应用的开发。

Deis、Flynn 和 Dokku 都是开源容器 PaaS 的代表。
容器支持技术
下面这些技术被用于支持基于容器的基础设施。
容器支持技术包括容器网络、服务发现、监控、数据管理、日志管理、安全性。

容器网络
容器的出现使网络拓扑变得更加动态和复杂。用户需要专门的解决方案来管理容器与容器、容器与其他实体之间的连通性和隔离性。

docker network 是 Docker 原生的网络解决方案。除此之外,我们还可以采用第三方开源解决方案,例如 flannel、weave 和 calico。不同的方案设计和实现方式不同,各有优势和特点,我们可以根据实际需要来选型。
服务发现
动态变化是微服务应用的一大特点。当负载增加时,集群会自动创建新的容器;负载减小,多余的容器会被销毁。容器也会根据 host 的资源使用情况在不同 host 中迁移,容器的 IP 和端口也会随之发生变化。
在这种动态的环境下,必须要有一种机制让 client 能够知道如何访问容器提供的服务。这就是服务发现技术要完成的工作。
服务发现会保存容器集群中所有微服务最新的信息,比如 IP 和端口,并对外提供 API,提供服务查询功能。

etcd、consul 和 zookeeper 是服务发现的典型解决方案。
监控
监控对于基础架构非常重要,而容器的动态特征对监控提出更多挑战。
针对容器环境,已经涌现出很多监控工具和方案。

- Docker 原生的命令行监控工具:docker ps/top/stats。
- Docker stats API:用户可以通过 HTTP 请求获取容器的状态信息。
- 其他开源的容器监控方案:sysdig、cAdvisor/Heapster、Weave Scope。
数据管理
容器经常会在不同的 host 之间迁移,如何保证持久化数据也能够动态迁移,是 Flocker 这类数据管理工具提供的能力。

日志管理
日志为问题排查和事件管理提供了重要依据。

- Docker 原生日志工具:docker logs。
- logspout:对日志提供了路由功能,它可以收集不同容器的日志并转发给其他工具进行后处理。
安全性
对于年轻的容器,安全性一直是业界争论的焦点。

OpenSCAP 能够对容器镜像进行扫描,发现潜在的漏洞。
1.4 安装 docker
直接操作的环境: https://labs.play-with-docker.com/
环境选择
容器需要管理工具、runtime 和操作系统,我们的选择如下:
- 管理工具 - Docker Engine,Docker 最流行使用最广泛。
- runtime - runc,Docker 的默认 runtime
- 操作系统 - CentOS Stream8
安装操作系统
基于 CentOS-Stream-8 模板克隆一个虚拟机命名为 docker,并配置静态 IP 192.168.108.30,主机名改为 docker。





配置静态 IP 和主机名
[root@localhost ~]# hostnamectl set-hostname docker
[root@localhost ~]# nmcli connection modify ens160 ipv4.method manual ipv4.addresses 192.168.108.30/24 ipv4.gateway 192.168.108.2 ipv4.dns 192.168.108.2 autoconnect yes
[root@localhost ~]# nmcli con up ens160
安装 Docker
Docker 支持几乎所有的 Linux 发行版,也支持 Mac 和 Windows。各操作系统的安装方法可以访问: https://docs.docker.com/engine/installation/
卸载旧版本 (可选)
[root@docker ~]# yum remove docker-ce
安装必要工具
# devicemapper 存储驱动已经在 docker 18.09 版本中被废弃,所以在后续的 Docker 安装中无需安装 devicemapper 支持。
[root@localhost ~]# yum install -y yum-utils device-mapper-persistent-data lvm2 vim
[root@localhost ~]# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
Adding repo from: https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@localhost ~]# yum makecache
Docker CE Stable - 57 kB/s | 66 kB 00:01
CentOS Stream 8 - 8.3 kB/s | 3.9 kB 00:00
CentOS Stream 8 - 9.8 kB/s | 4.4 kB 00:00
Metadata cache created.
allinone 部署 - 安装软件
[root@docker ~]# yum install -y docker-ce
配置服务
[root@docker ~]# systemctl enable docker.service --now
验证安装
-
查看 docker 版本
[root@docker ~]# docker --version
Docker version 26.1.3, build b72abbb -
验证 docker 状态
[root@docker ~]# systemctl status docker
docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
Active: active (running) since Wed 2025-09-03 13:51:07 CST; 36s ago
Docs: https://docs.docker.com
Main PID: 1813 (dockerd)
Tasks: 10
Memory: 135.7M
CGroup: /system.slice/docker.service
└─1813 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
配置镜像加速器 (华为云)
- 访问华为云官网:https://www.huaweicloud.com
- 进入容器镜像服务 SWR:https://console.huaweicloud.com/swr
- 获取镜像加速器地址(示例地址:https://054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com)
- 配置镜像加速器:



[root@docker ~]# vi /etc/docker/daemon.json
{
"registry-mirrors": [ "https://9cf68de5543c4ca1bde5c79205e4619f.mirror.swr.myhuaweicloud.com" ]
}
# 按"Esc",输入:wq保存并退出。
# 重启容器引擎
[root@docker ~]# systemctl restart docker
# 确认配置结果
[root@docker ~]# docker info
Client: Docker Engine - Community
Version: 26.1.3
Context: default
Debug Mode: false
Plugins:
buildx: Docker Buildx (Docker Inc.)
Version: v0.14.0
Path: /usr/libexec/docker/cli-plugins/docker-buildx
compose: Docker Compose (Docker Inc.)
Version: v2.27.0
Path: /usr/libexec/docker/cli-plugins/docker-compose
Server:
Containers: 18
Running: 9
Paused: 0
Stopped: 9
Images: 16
Server Version: 26.1.3
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
userxattr: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Cgroup Version: 1
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
Swarm: inactive
Runtimes: io.containerd.runc.v2 runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 8b3b7ca2e5ce38e8f31a34f35b2b68ceb8470d89
runc version: v1.1.12-0-g51d5e94
init version: de40ad0
Security Options:
seccomp
Profile: builtin
Kernel Version: 4.18.0-553.6.1.el8.x86_64
Operating System: CentOS Stream 8
OSType: linux
Architecture: x86_64
CPUs: 4
Total Memory: 7.486GiB
Name: docker
ID: 082711dc-abbf-4f93-8afc-1e59665565d0
Docker Root Dir: /var/lib/docker
Debug Mode: false
Experimental: false
Insecure Registries:
192.168.108.30
127.0.0.0/8
Registry Mirrors:
https://9cf68de5543c4ca1bde5c79205e4619f.mirror.swr.myhuaweicloud.com/ #####看这
Live Restore Enabled: false
运行第一个容器
环境就绪,马上运行第一个容器,执行命令:
[root@docker ~]# docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
17eec7bbc9d7: Pull complete
Digest: sha256:a0dfb02aac212703bfcb339d77d47ec32c8706ff250850ecc0e19c8737b18567
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
其过程可以简单的描述为:
- 从本地查找 hello-world 镜像,没找到。
- 从 Docker Hub 下载 hello-world 镜像。
- 启动 hello-world 容器。
清空实验环境
[root@docker ~]# docker rm -f $(docker ps -aq) # 删除所有容器
[root@docker ~]# docker rmi -f hello-world # 删除镜像 hello-world
此刻 docker 环境没有问题了,关机拍摄快照:
- 关闭虚拟机。
- 右键虚拟机 → 快照 → 拍摄快照。
- 名称输入 "docker ok",点击 "拍摄快照(T)"。

运行第二个容器
[root@docker ~]# docker run -d -p 80:80 httpd
Unable to find image 'httpd:latest' locally
latest: Pulling from library/httpd
852e50cd189d: Pull complete
67d51c33d390: Pull complete
b0ad2a3b9567: Pull complete
136f1f71f30c: Pull complete
01f8ace29294: Pull complete
Digest: sha256:fddc534b7f6bb6197855be559244adb11907d569aae1283db8e6ce8bb8f6f456
Status: Downloaded newer image for httpd:latest
817f24cca3514568f9b7a7cac6f183734077fb74caf4c62502acd2a4d0d29520
其过程可以简单的描述为:
- 从 Docker Hub 下载 httpd 镜像。镜像中已经安装好了 Apache HTTP Server。
- 启动 httpd 容器,并将容器的 80 端口映射到 host 的 80 端口。
验证容器是否正常工作
[root@docker ~]# curl 192.168.108.30
<html><body><h1>It works!</h1></body></html>
也可以通过浏览器访问 http://192.168.108.30,会显示 "It works!",说明容器的 http 服务正常运行。
1.5 docker C/S 分离部署
基于 CentOS-Stream-8 模板制作.pdf 这个实验手册做出来的模板克隆两个虚拟机,命名为 docker_client 和 docker_server。
docker server 端配置

配置主机名和静态 IP
[root@localhost ~]# hostnamectl set-hostname docker_server
[root@localhost ~]# nmcli connection modify ens160 ipv4.method manual ipv4.addresses 192.168.108.30/24 ipv4.gateway 192.168.108.2 ipv4.dns 192.168.108.2 autoconnect yes
[root@localhost ~]# nmcli con up ens160
安装 Docker
[root@docker_server ~]# yum install -y yum-utils device-mapper-persistent-data lvm2 vim
[root@docker_server ~]# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
Adding repo from: https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@docker_server ~]# yum makecache
[root@docker_server ~]# yum install -y docker-ce
[root@docker_server ~]# systemctl enable docker.service --now
配置镜像加速器(以华为云为例)
[root@docker_server ~]# vi /etc/docker/daemon.json
{
"registry-mirrors": [ "https://9cf68de5543c4ca1bde5c79205e4619f.mirror.swr.myhuaweicloud.com" ]
}
[root@docker_server ~]# systemctl restart docker
配置 Docker 监听远程端口
[root@docker_server ~]# vim /usr/lib/systemd/system/docker.service
# 修改 ExecStart 行,添加 -H tcp://0.0.0.0:2375
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375
[root@docker_server ~]# systemctl daemon-reload
[root@docker_server ~]# systemctl restart docker.service
[root@docker_server ~]# systemctl stop firewalld

docker client 端配置
配置主机名和静态 IP
[root@localhost ~]# hostnamectl set-hostname docker_client
[root@localhost ~]# nmcli connection modify ens160 ipv4.method manual ipv4.addresses 192.168.108.31/24 ipv4.gateway 192.168.108.2 ipv4.dns 192.168.108.2 autoconnect yes
[root@localhost ~]# nmcli con up ens160
仅安装 Docker 客户端
[root@docker-client ~]# yum install -y yum-utils device-mapper-persistent-data lvm2 vim
[root@docker-client ~]# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
Adding repo from: https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@docker-client ~]# yum makecache
[root@docker-client ~]# yum install -y docker-ce-cli
连接服务端运行容器
[root@docker-client ~]# docker -H 192.168.108.30 run hello-world
说明:client 只做管理,image 和 container 存储在 server 端。
第 2 章 容器架构
2.1 容器 What,Why,How
学习任何东西都可以按照 3W 的框架进行,容器技术也是一样,先回答 What、Why 和 How 这三个问题。
容器发展史
1979 年,在 Unix V7 的开发过程中,引入 Chroot Jail 以及 Chroot 系统调用。Chroot jail 被用于 "Change Root",它被认为是最早的容器化技术之一。它允许您将进程及其子进程与操作系统的其余部分隔离开来。这种隔离的唯一问题是根进程 (root process) 可以轻松地退出 chroot。它从未考虑实现安全机制。
1982 年,Chroot 加入 BSD。
2000 年,在 FreeBSD OS 中引入 FreeBSD Jail,旨在为简单的 Chroot 文件隔离带来更多安全性。FreeBSD Jail 允许管理员将 FreeBSD OS 划分为几个独立的小型系统 - 称为 "jails",还可以为每个小型系统分配 IP 地址。
2001 年,Linux 内核具有操作系统级的虚拟化的功能以后推出了 Linux VServer。它使用了类似 chroot 的机制与 "安全上下文"("security context") 以及操作系统虚拟化 (容器化) 相结合来提供虚拟化解决方案,允许您在单个 Linux 发行版上运行多个 Linux 发行版。
2004 年,Solaris Containers 问世,它使用系统资源控制和 "区域"(zone) 实现边界分离。
2006 年,Google 推出 Process Containers,旨在限制、计算和隔离一组进程的资源使用 (CPU、内存、磁盘 I/O、网络)。2007 年,命名为控制组 - cgroups,最终合并到 Linux 内核 2.6.24。
2008 年,LXC (LinuX Containers) 是第一个 Linux 容器管理器,使用 cgroups 和命名空间在单个 Linux 内核上运行多个容器。
2013 年,Docker 推出了的第一个版本。在 LXC 的基础上,Docker 进一步优化了容器的使用体验。后来用自己的库 libcontainer 替换了该容器管理器。
2014 年,Google 推出容器 LMCTFY (Let me contain that for you),谷歌容器栈的开源版本,提供 Linux 应用程序容器。谷歌工程师一直在与 Docker 合作 libcontainer,并将核心概念和抽象移植到 libcontainer。因此没有积极开发 LMCTFY 项目,未来 LMCTFY 项目的核心可能会被 libcontainer 取代。谷歌是容器化行业的领导者。谷歌的一切都在容器上运行。每周有超过 20 亿个容器在 Google 基础设施上运行。
2014 年 12 月,CoreOS 发布并开始支持 rkt。
What - 什么是容器?
容器是一种轻量级、可移植、自包含的软件打包技术,使应用程序可以在几乎任何地方以相同的方式运行。开发人员在自己笔记本上创建并测试好的容器,无需任何修改就能够在生产系统的虚拟机、物理服务器或公有云主机上运行。
容器与虚拟机
谈到容器,就不得不将它与虚拟机进行对比,因为两者都是为应用提供封装和隔离。
容器由两部分组成:
- 应用程序本身
- 依赖:比如应用程序需要的库或其他软件
容器在 Host 操作系统的用户空间中运行,与操作系统的其他进程隔离。这一点显著区别于的虚拟机。传统的虚拟化技术,比如 VMWare、KVM、Xen,目标是创建完整的虚拟机。为了运行应用,除了部署应用本身及其依赖 (通常几十 MB),还得安装整个操作系统 (几十 GB)。
下图展示了二者的区别。

如图所示,由于所有的容器共享同一个 Host OS,这使得容器在体积上要比虚拟机小很多。另外,启动容器不需要启动整个操作系统,所以容器部署和启动速度更快,开销更小,也更容易迁移。
Why - 为什么需要容器?
为什么需要容器?容器到底解决的是什么问题?
简要的答案是:容器使软件具备了超强的可移植能力。
容器解决的问题
我们来看看今天的软件开发面临着怎样的挑战?
如今的系统在架构上较十年前已经变得非常复杂了。以前几乎所有的应用都采用三层架构 (Presentation/Application/Data),系统部署到有限的几台物理服务器上 (Web Server/Application Server/Database Server)。
而今天,开发人员通常使用多种服务 (比如 MQ、Cache、DB) 构建和组装应用,而且应用很可能会部署到不同的环境,比如虚拟服务器、私有云和公有云。

一方面应用包含多种服务,这些服务有自己所依赖的库和软件包;另一方面存在多种部署环境,服务在运行时可能需要动态迁移到不同的环境中。这就产生了一个问题:
如何让每种服务能够在所有的部署环境中顺利运行?
于是我们得到了下面这个矩阵:

各种服务和环境通过排列组合产生了一个大矩阵。开发人员在编写代码时需要考虑不同的运行环境,运维人员则需要为不同的服务和平台配置环境。对他们双方来说,这都是一项困难而艰巨的任务。
如何解决这个问题呢?
聪明的技术人员从传统的运输行业找到了答案。
几十年前,运输业面临着类似的问题。

每一次运输,货主与承运方都会担心因货物类型的不同而导致损失,比如几个铁桶错误地压在了一堆香蕉上。另一方面,运输过程中需要使用不同的交通工具也让整个过程痛苦不堪:货物先装上车运到码头,卸货,然后装上船,到岸后又卸下船,再装上火车,到达目的地,最后卸货。一半以上的时间花费在装、卸货上,而且搬上搬下还容易损坏货物。
这同样也是一个 NxM 的矩阵。

幸运的是,集装箱的发明解决这个难题。

任何货物,无论钢琴还是保时捷,都被放到各自的集装箱中。集装箱在整个运输过程中都是密封的,只有到达最终目的地才被打开。标准集装箱可以被高效地装卸、重叠和长途运输。现代化的起重机可以自动在卡车、轮船和火车之间移动集装箱。集装箱被誉为运输业与世界贸易最重要的发明。

Docker 将集装箱思想运用到软件打包上,为代码提供了一个基于容器的标准化运输系统。Docker 可以将任何应用及其依赖打包成一个轻量级、可移植、自包含的容器。容器可以运行在几乎所有的操作系统上。

其实,"集装箱" 和 "容器" 对应的英文单词都是 "Container"。"容器" 是国内约定俗成的叫法,可能是因为容器比集装箱更抽象,更适合软件领域的缘故吧。
我个人认为:在老外的思维中,"Container" 只用到了集装箱这一个意思,Docker 的 Logo 不就是一堆集装箱吗?

Docker 的特性
我们可以看看集装箱思想是如何与 Docker 各种特性相对应的。
| 特性 | 集装箱 | Docker |
|---|---|---|
| 打包 对象 | 几乎任何货物 | 任何软件及其依赖 |
| 硬件 依赖 | 标准形状和接口允许集装箱被装卸到各种交通工具,整个运输过程无需打开 | 容器无需修改便可运行在几乎所有的平台上 -- 虚拟机、物理机、公有云、私有云 |
| 隔离 性 | 集装箱可以重叠起来一起运输,香蕉再也不会被铁桶压烂了 | 资源、网络、库都是隔离的,不会出现依赖问题 |
| 自动 化 | 标准接口使集装箱很容易自动装卸和移动 | 提供 run、start、stop 等标准化操作,非常适合自动化 |
| 高效 性 | 无需开箱,可在各种交通工具间快速搬运 | 轻量级,能够快速启动和迁移 |
| 职责 分工 | 货主只需考虑把什么放到集装箱里;承运方只需关心怎样运输集装箱 | 开发人员只需考虑怎么写代码;运维人员只需关心如何配置基础环境 |
容器的优势
对于开发人员 - Build Once, Run Anywhere
容器意味着环境隔离和可重复性。开发人员只需为应用创建一次运行环境,然后打包成容器便可在其他机器上运行。另外,容器环境与所在的 Host 环境是隔离的,就像虚拟机一样,但更快更简单。
对于运维人员 - Configure Once, Run Anything
只需要配置好标准的 runtime 环境,服务器就可以运行任何容器。这使得运维人员的工作变得更高效、一致和可重复。容器消除了开发、测试、生产环境的不一致性。
How - 容器是如何工作的?
从下节开始我们将学习容器核心知识的最主要部分。首先会介绍 Docker 的架构,然后分章节详细讨论 Docker 的镜像、容器、网络和存储。
容器与虚拟机比较
谈到容器,就不得不将它与虚拟机进行对比,因为两者都是为应用提供封装和隔离。
容器由两部分组成:
- 应用程序本身
- 依赖:比如应用程序需要的库或其他软件
容器在 Host 操作系统的用户空间中运行,与操作系统的其他进程隔离。这一点显著区别于的虚拟机。
传统的虚拟化技术,比如 VMWare、KVM、Xen,目标是创建完整的虚拟机。为了运行应用,除了部署应用本身及其依赖 (通常几十 MB),还得安装整个操作系统 (几十 GB)。
下图展示了二者的区别:

如图所示:
所有的容器共享同一个 Host OS,这使得容器在体积上要比虚拟机小很多。
另外,启动容器不需要启动整个操作系统,容器部署和启动速度更快,开销更小,也更容易迁移。
容器可以在核心 CPU 本地运行指令,而不需要任何专门的解释机制。与虚拟化相比,这样既不需要指令级模拟,也不需要即时编译。
| 特性 | 容器 | 虚拟机 |
|---|---|---|
| 启动速度 | 秒级 | 分钟级 |
| 性能 | 接近原生 | 较弱 |
| 内存代价 | 很小 | 较多 |
| 硬盘使用 | 一般为 MB | 一般为 GB |
| 运行密度 | 单击支持上千个容器 | 一般几十个 |
| 隔离性 | 安全隔离 | 完全隔离 |
| 迁移性 | 优秀 | 一般 |
2.2 Docker 介绍
Docker 是基于 Go 语言实现的开源容器项目,诞生于 2013 年年初,最初发起者是 dotCloud 公司。Docker 自开源后受到广泛的关注和讨论,逐渐形成了围绕 Docker 容器的生态体系。由于 Docker 在业界造成的影响力实在太大,dotCloud 公司后来也直接改名为 Docker Inc,并专注于 Docker 相关技术和产品的开发。
Docker 提供了各种容器管理工具 (如分发、版本、移植等),让用户无需关注底层的操作,可以更简单明了地管理和使用容器;其次,Docker 引入分层文件系统构建和高效的镜像机制降低了迁移难度,极大地提升了用户体验。用户操作 Docker 容器就像操作应用自身一样简单。
简单地讲,可以将 Docker 容器理解为一种轻量级的沙盒 (sandbox)。每个容器内运行着一个应用,不同的容器相互隔离,容器之间也可以通过网络互相通信。容器的创建和停止都十分快速,几乎跟创建和终止原生应用一致;另外,容器自身对系统资源的额外需求也十分有限,远远低于传统虚拟机。很多时候,甚至直接把容器当作应用本身也没有任何问题。

Docker 版本
Docker CE 在 17.03 版本之前叫 Docker Engine,Docker Engine 的版本号范围:0.1.0~1.13.1。
在 2017 年 3 月 2 日,docker 团队宣布企业版 Docker Enterprise Edition (EE) 发布。为了一致,免费的 Docker Engine 改名为 Docker Community Edition (CE),并且采用基于时间的版本号方案。就在这一天,Docker EE 和 Docker CE 的 17.03 版本发布,这也是第一个采用新的版本号方案的版本。

Docker CE/EE 每个季度发布一次季度版本,也就是说每年会发布 4 个季度版本,17.03、17.06、17.09、17.12 就是 2017 年的 4 个季度版本的版本号。同时 Docker CE 每个月还会发布一个 EDGE 版本,比如 17.04、17.05、17.07、17.08、17.10、17.11。
Docker CE 季度版本自发布后会有 4 个月的维护期;Docker EE 季度版本自发布后会有 12 个月的维护期。
在基于时间的发布方案中,版本号格式为:YY.MM.patch,YY.MM 代表年月,patch 代表补丁号,从 0 开始。在季度版本 (如 17.03) 的维护期内,bug 修复相关的更新会以 patch 递增的方式发布,比如 17.03.0->17.03.1->17.03.2。
Docker 核心概念
Docker 的大部分操作都围绕着它的三大核心概念 -- 镜像 、容器 和仓库而展开。
Docker 镜像
Docker 镜像类似于虚拟机镜像,可以将它理解为一个只读的模板。例如,一个镜像可以包含一个基本的操作系统环境,里面仅安装了 Apache 应用程序 (或用户需要的其他软件)。可以把它称为一个 Apache 镜像。镜像是创建 Docker 容器的基础。通过版本管理和增量的文件系统,Docker 提供了一套十分简单的机制创建和更新现有的镜像,用户甚至可以从网上下载一个已经做好的应用镜像,并直接使用。
Docker 容器
Docker 容器类似于一个轻量级的沙箱,Docker 利用容器来运行和隔离应用。容器是从镜像创建的应用运行实例。可以将其启动、开始、停止、删除,而这些容器都是彼此相互隔离的、互不可见的。可以把容器看做是一个简易版的 Linux 系统环境 (包括 root 用户权限、进程空间、用户空间和网络空间等) 以及运行在其中的应用程序打包而成的盒子。
Docker 仓库
Docker 仓库类似于代码仓库,它是 Docker 集中存放镜像文件的场所。仓库注册服务器存放着很多类镜像,每类镜像包括多个镜像文件,通过不同的标签 (tag) 来进行区分。例如存放 Ubuntu 操作系统镜像的分类中可能包括 18.04、16.04、14.04、12.04 等不同版本的镜像。
根据所存储的镜像公开分享与否,Docker 仓库可以分为:
- 公开仓库 (Public)
- 私有仓库 (Private)
目前,最大的公开仓库是官方提供的 Docker Hub,其中存放了数量庞大的镜像供用户下载。国内不少云服务提供商 (如时速云、阿里云等) 也提供了仓库的本地源,可以提供稳定的国内访问。
当然,用户如果不希望公开分享自己的镜像文件,Docker 也支持用户在本地网络内创建一个只能自己访问的私有仓库。当用户创建了自己的镜像之后就可以使用 push 命令将它上传到指定的公有或者私有仓库。这样用户下次在另外一台机器上使用该镜像时,只需要将其从仓库上 pull 下来就可以了。
2.3 Docker 架构详解
完整的 Docker 由以下几部分构成:
(1) 守护进程 (Daemon):Docker 守护进程 (dockerd) 侦听 Docker API 请求并管理 Docker 对象,如图像、容器、网络和卷。守护进程还可以与其他守护进程通信来管理 Docker 服务。
(2) REST API:主要与 Docker Daemon 进行交互,比如 Docker Cli 或者直接调用 REST API;
(3) 客户端 (Docker Client):它是与 Docker 交互的主要方式。通过命令行接口 (CLI) 客户端 (docker 命令),客户机将命令通过 REST API 发送给并执行其命令;
(4) Register Repository 镜像仓库:Docker 注册表存储 Docker 镜像,可以采用 Docker Hub 是公共注册仓库,或者采用企业内部自建的 Harbor 私有仓库;
(5) Image 镜像:映像是一个只读模板,带有创建 Docker 容器的指令。映像通常基于另一个映像,还需要进行一些额外的定制,你可以通过 Docker Hub 公共镜像仓库进行拉取对应的系统或者应用镜像;
(6) Container 容器:容器是映像的可运行实例。您可以使用 Docker API 或 CLI 创建、启动、停止、移动或删除容器。您可以将一个容器连接到一个或多个网络,将存储附加到它,甚至根据它的当前状态创建一个新映像;
(7) Services:Docker 引擎支持集群模式服务允许您跨多个 Docker 守护进程扩展管理容器,服务允许您定义所需的状态,例如在任何给定时间必须可用的服务副本的数量。默认情况下,服务在所有工作节点之间进行负载平衡。对于使用者来说 Docker 服务看起来是一个单独的应用程序;

Docker 的核心组件包括
- Docker 客户端 - Client
- Docker 服务器 - Docker daemon
- Docker 镜像 - Image
- Registry
- Docker 容器 - Container
Docker 架构
Docker 采用的是 Client/Server 架构。客户端向服务器发送请求,服务器负责构建、运行和分发容器。客户端和服务器可以运行在同一个 Host 上,客户端也可以通过 socket 或 REST API 与远程的服务器通信。
下图展示了 Docker 架构:

Docker 内部具体实现
- 用户是使用 Docker Client 与 Docker Daemon 建立通信,并发送请求给后者。
- Docker Daemon 作为 Docker 架构中的主体部分,首先提供 Docker Server 的功能使其可以接受 Docker Client 的请求。
- Docker Engine 执行 Docker 内部的一系列工作,每一项工作都是以一个 Job 的形式存在。
- Job 的运行过程中,当需要容器镜像时,则从 DockerRegistry 中下载镜像,并通过镜像管理驱动 Graph driver 将下载镜像以 Graph 的形式存储。
- 当需要为 Docker 创建网络环境时,通过网络管理驱动 Networkdriver 创建并配置 Docker 容器网络环境。
- 当需要限制 Docker 容器运行资源或执行用户指令等操作时,则通过 Exec driver 来完成。
- Libcontainer 是一项独立的容器管理包,Networkdriver 以及 Execdriver 都是通过 Libcontainer 来实现具体对容器进行的操作。

Docker Client
最常用的 Docker 客户端是 docker 命令。通过 docker 我们可以方便地在 Host 上构建和运行容器。docker 支持很多操作 (子命令),后面会逐步用到。
[root@docker ~]# docker Usage: docker [OPTIONS] COMMAND
A self-sufficient runtime for containers
Common Commands:
run Create and run a new container from an image
exec Execute a command in a running container
ps List containers
build Build an image from a Dockerfile
pull Download an image from a registry
push Upload an image to a registry
login Log in to a registry
logout Log out from a registry
search Search Docker Hub for images
version Show the Docker version information
info Display system-wide information
Management Commands:
builder Manage builds
buildx* Docker Buildx
compose* Docker Compose
container Manage containers
context Manage contexts
image Manage images
manifest Manage Docker image manifests and manifest lists
network Manage networks
plugin Manage plugins
system Manage Docker
trust Manage trust on Docker images
volume Manage volumes
Swarm Commands:
swarm Manage Swarm
Commands:
attach Attach local standard input, output, and error streams to a running container
commit Create a new image from a container's changes
cp Copy files/folders between a container and the local filesystem
create Create a new container
diff Inspect changes to files or directories on a container's filesystem
events Get real time events from the server
export Export a container's filesystem as a tar archive
history Show the history of an image
import Import the contents from a tarball to create a filesystem image
inspect Return low-level information on Docker objects
kill Kill one or more running containers
load Load an image from a tar archive or STDIN
logs Fetch the logs of a container
pause Pause all processes within one or more containers
port List port mappings or a specific mapping for the container
rename Rename a container
restart Restart one or more containers
rm Remove one or more containers
rmi Remove one or more images
save Save one or more images to a tar archive (streamed to STDOUT by default)
start Start one or more stopped containers
stats Display a live stream of container(s) resource usage statistics
stop Stop one or more running containers
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
top Display the running processes of a container
unpause Unpause all processes within one or more containers
update Update configuration of one or more containers
wait Block until one or more containers stop, then print their exit codes
Global Options:
--config string Location of client config files (default "/root/.docker")
-c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with "docker context use")
-D, --debug Enable debug mode
-H, --host list Daemon socket to connect to
-l, --log-level string Set the logging level ("debug", "info", "warn", "error", "fatal") (default "info")
--tls Use TLS; implied by --tlsverify
--tlscacert string Trust certs signed only by this CA (default "/root/.docker/ca.pem")
--tlscert string Path to TLS certificate file (default "/root/.docker/cert.pem")
--tlskey string Path to TLS key file (default "/root/.docker/key.pem")
--tlsverify Use TLS and verify the remote
-v, --version Print version information and quit
Run 'docker COMMAND --help' for more information on a command.
For more help on how to use Docker, head to https://docs.docker.com/go/guides/
除了 docker 命令行工具,用户也可以通过 REST API 与服务器通信。
Docker 服务器
Docker daemon 是服务器组件,以 Linux 后台服务的方式运行。
[root@docker ~]# systemctl status docker.service
docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
Active: active (running) since Mon 2024-09-09 21:40:20 CST; 24h ago
Docs: https://docs.docker.com
Main PID: 2309 (dockerd)
Tasks: 10
Memory: 136.0M
CGroup: /system.slice/docker.service
└─2309 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
Sep 09 21:40:16 docker systemd[1]: Starting Docker Application Container Engine...
Sep 09 21:40:17 docker dockerd[2309]: time="2024-09-09T21:40:17.800157335+08:00" level=info msg="Start..."
Sep 09 21:40:18 docker dockerd[2309]: time="2024-09-09T21:40:18.196566715+08:00" level=info msg="[gra...]"
Sep 09 21:40:18 docker dockerd[2309]: time="2024-09-09T21:40:18.208666863+08:00" level=info msg="Load..."
Sep 09 21:40:19 docker dockerd[2309]: time="2024-09-09T21:40:19.587647943+08:00" level=info msg="Defa..."
Sep 09 21:40:19 docker dockerd[2309]: time="2024-09-09T21:40:19.881267095+08:00" level=info msg="Load..."
Sep 09 21:40:19 docker dockerd[2309]: time="2024-09-09T21:40:19.956577565+08:00" level=info msg="Dock..."
Sep 09 21:40:19 docker dockerd[2309]: time="2024-09-09T21:40:19.957045036+08:00" level=info msg="Daem..."
Sep 09 21:40:20 docker dockerd[2309]: time="2024-09-09T21:40:20.014584201+08:00" level=info msg="API ..."
Sep 09 21:40:20 docker systemd[1]: Started Docker Application Container Engine.
Docker daemon 运行在 Docker host 上,负责创建、运行、监控容器,构建、存储镜像。
默认配置下,Docker daemon 只能响应来自本地 Host 的客户端请求。如果要允许远程客户端请求,需要在配置文件中打开 TCP 监听,步骤如下:
配置服务
[root@docker ~]# vim /usr/lib/systemd/system/docker.service
# 在ExecStart参数中最后添加 -H tcp://0.0.0.0:2375,允许来自任意 IP 的客户端连接。docker默认监听2375
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375
[root@docker ~]# systemctl daemon-reload # 重启 Docker daemon
[root@docker ~]# systemctl restart docker.service
[root@docker ~]# systemctl stop firewalld
[root@docker ~]# systemctl disable firewalld
验证
[root@docker ~]# yum install lsof
[root@docker ~]# lsof -i :2375
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
dockerd 5440 root 3u IPv6 44991 0t0 TCP *:docker (LISTEN)
服务器 IP 为 192.168.108.30,客户端在命令行里加上 -H 参数,即可与远程服务器通信。
info 子命令用于查看 Docker 服务器的信息。
[root@docker-client ~]# docker -H 192.168.108.30 info
Docker 镜像
可将 Docker 镜像看作只读模板,通过它可以创建 Docker 容器。
例如某个镜像可能包含一个 Ubuntu 操作系统、一个 Apache HTTP Server 以及用户开发的 Web 应用。镜像有多种生成方法:
- 可以从无到有开始创建镜像
- 也可以下载并使用别人创建好的现成的镜像
- 还可以在现有镜像上创建新的镜像
我们可以将镜像的内容和创建步骤描述在一个文本文件中,这个文件被称作 Dockerfile,通过执行 docker build 命令可以构建出 Docker 镜像,后面我们会讨论。
Docker 容器
Docker 容器就是 Docker 镜像的运行实例。
用户可以通过 CLI (docker) 或是 API 启动、停止、移动或删除容器。可以这么认为,对于应用软件,镜像是软件生命周期的构建和打包阶段,而容器则是启动和运行阶段。
Registry
Registry 是存放 Docker 镜像的仓库,Registry 分私有和公有两种。
Docker Hub(https://hub.docker.com/) 是默认的 Registry,由 Docker 公司维护,上面有数以万计的镜像,用户可以自由下载和使用。
出于对速度或安全的考虑,用户也可以创建自己的私有 Registry。后面我们会学习如何搭建私有 Registry。
docker pull 命令可以从 Registry 下载镜像。
docker run 命令则是先下载镜像 (如果本地没有),然后再启动容器。
下一节我们通过一个例子来看各个组件是如何协调工作的。
2.4 Docker 组件如何协作?
还记得我们运行的第一个容器吗?现在通过它来体会一下 Docker 各个组件是如何协作的。
容器启动过程如下:

- Docker 客户端执行 docker run 命令。
- Docker daemon 发现本地没有 httpd 镜像。
- daemon 从 Docker Hub 下载镜像。
- 下载完成,镜像 httpd 被保存到本地。
- Docker daemon 启动容器。
docker images 可以查看到 httpd 已经下载到本地:
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd latest 9cb0a2315602 7 weeks ago 148MB
docker ps 或者 docker container ls 显示容器正在运行:

小结
Docker 借鉴了集装箱的概念。标准集装箱将货物运往世界各地,Docker 将这个模型运用到自己的设计哲学中,唯一不同的是:集装箱运输货物,而 Docker 运输软件。
每个容器都有一个软件镜像,相当于集装箱中的货物。容器可以被创建、启动、关闭和销毁。和集装箱一样,Docker 在执行这些操作时,并不关心容器里到底装的什么,它不管里面是 Web Server,还是 Database。
用户不需要关心容器最终会在哪里运行,因为哪里都可以运行。
开发人员可以在笔记本上构建镜像并上传到 Registry,然后测试人员将镜像下载到物理或虚拟机做测试,最终容器会部署到生产环境。
使用 Docker 以及容器技术,我们可以快速构建一个应用服务器、一个消息中间件、一个数据库、一个持续集成环境。因为 Docker Hub 上有我们能想到的几乎所有的镜像。
不知大家是否意识到,潘多拉盒子已经被打开。容器不但降低了我们学习新技术的门槛,更提高了效率。
如果你是一个运维人员,想研究负载均衡软件 HAProxy,只需要执行 docker run haproxy,无需繁琐的手工安装和配置既可以直接进入实战。
如果你是一个开发人员,想学习怎么用 django 开发 Python Web 应用,执行 docker run django,在容器里随便折腾吧,不用担心会搞乱 Host 的环境。
不夸张的说:容器大大提升了 IT 人员的幸福指数。
第 3 章 镜像
3.1 最小的镜像
镜像是 Docker 容器的基石,容器是镜像的运行实例,有了镜像才能启动容器。
本章内容安排如下:
- 首先通过研究几个典型的镜像,分析镜像的内部结构。
- 然后学习如何构建自己的镜像。
- 最后介绍怎样管理和分发镜像。
镜像的内部结构
为什么我们要讨论镜像的内部结构?
如果只是使用镜像,当然不需要了解,直接通过 docker 命令下载和运行就可以了。但如果我们想创建自己的镜像,或者想理解 Docker 为什么是轻量级的,就非常有必要学习这部分知识了。
我们从一个最小的镜像开始吧。
hello-world - 最小的镜像
hello-world 是 Docker 官方提供的一个镜像,通常用来验证 Docker 是否安装成功。
我们先通过 docker pull 从 Docker Hub 下载它:
[root@docker ~]# docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:91fb4b041da273d5a3273b6d587d62d518300a6ad268b28628f74997b93171b2
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest
用 docker images 命令查看镜像的信息:
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest d2c94e258dcb 16 months ago 13.3kB
hello-world 镜像竟然还不到 14KB!
通过 docker run 运行:
[root@docker ~]# docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
其实我们更关心 hello-world 镜像包含哪些内容。
Dockerfile 是镜像的描述文件,定义了如何构建 Docker 镜像。Dockerfile 的语法简洁且可读性强,后面我们会专门讨论如何编写 Dockerfile。
hello-world 的 Dockerfile 内容如下
FROM scratch
COPY hello /
CMD ["/hello"]

只有短短三条指令:
- FROM scratch:此镜像是从白手起家,从 0 开始构建。
- COPY hello /:将文件 "hello" 复制到镜像的根目录。
- CMD ["/hello"]:容器启动时,执行 /hello。
镜像 hello-world 中就只有一个可执行文件 "hello",其功能就是打印出 "Hello from Docker ..." 等信息。
/hello 就是文件系统的全部内容,连最基本的 /bin、/usr、/lib、/dev 都没有。
hello-world 虽然是一个完整的镜像,但它并没有什么实际用途。通常来说,我们希望镜像能提供一个基本的操作系统环境,用户可以根据需要安装和配置软件。这样的镜像我们称作 base 镜像。
我们下一节讨论 base 镜像。
3.2 base 镜像
上一节我们介绍了最小的 Docker 镜像,本节讨论 base 镜像。base 镜像有两层含义:
- 不依赖其他镜像,从 scratch 构建。
- 其他镜像可以之为基础进行扩展。
所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu、Debian、CentOS 等。

我们以 CentOS 为例考察 base 镜像包含哪些内容。
下载镜像
[root@docker ~]# docker pull centos:7
查看镜像信息
[root@docker ~]# docker images centos:7
REPOSITORY TAG IMAGE ID CREATED SIZE
centos 7 eeb6ee3f44bd 3 years ago 204MB
镜像大小不到 300MB。
等一下!一个 CentOS 才 204MB?平时我们安装一个 CentOS 至少都有几个 GB,怎么可能才 204MB!
相信这是几乎所有 Docker 初学者都会有的疑问,包括我自己。下面我们来解释这个问题。
Linux 操作系统的组成
Linux 操作系统由内核空间和用户空间组成。如下图所示:

内核空间是 kernel,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。
用户空间的文件系统是 rootfs,包含我们熟悉的 /dev、/proc、/bin 等目录。
对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。
而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。相比其他 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了,alpine 还不到 10MB。
我们平时安装的 CentOS 除了 rootfs 还会选装很多软件、服务、图形桌面等,需要好几个 GB 就不足为奇了。
base 镜像提供的是最小安装的 Linux 发行版。
下面是 CentOS 镜像的 Dockerfile 的内容:

第二行 ADD 指令添加到镜像的 tar 包就是 CentOS 7 的 rootfs。在制作镜像时,这个 tar 包会自动解压到 / 目录下,生成 /dev、/proc、/bin 等目录。
注:可在 Docker Hub 的镜像描述页面中查看 Dockerfile。
支持运行多种 Linux OS
不同 Linux 发行版的区别主要就是 rootfs。比如 Ubuntu 14.04 使用 upstart 管理服务,apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。这些都是用户空间上的区别,Linux kernel 差别不大。
所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统环境。

上图 Debian 和 BusyBox (一种嵌入式 Linux) 上层提供各自的 rootfs,底层共用 Docker Host 的 kernel。
这里需要说明的是:
-
base 镜像只是在用户空间与发行版一致,kernel 版本与发行版是不同的。例如 ubuntu 使用 3.x.x 的 kernel,如果 Docker Host 是 CentOS Stream 8(比如我们的实验环境),那么在 CentOS 容器中使用的实际是 Host 4.18.0 的 kernel。
[root@docker ~]# uname -r 4.18.0-553.6.1.el8.x86_64 # Host OS kernel 为 4.18.0 # 启动一个 ubuntu,ubuntu 内核正常应该与 host os(centos stream 8)不一致 [root@docker ~]# docker run -it ubuntu root@4264749aa4af:/# uname -r 4.18.0-553.6.1.el8.x86_64 # 容器 ubuntu 用的内核就是 docker host 内核 # 启动一个 centos:7,centos7 正常内核为 3.10 [root@docker ~]# docker run -it centos:7 [root@72397b60bb10 /]# uname -r 4.18.0-553.6.1.el8.x86_64 -
容器只能使用 Host 的 kernel,并且不能修改。所有容器都共用 host 的 kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。
下一节我们讨论镜像的分层结构。
3.3 镜像的分层结构
Docker 支持通过扩展现有镜像,创建新的镜像。
实际上,Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。比如我们现在构建一个新的镜像,Dockerfile 如下:

① 新镜像不再是从 scratch 开始,而是直接在 Debian base 镜像上构建。
② 安装 emacs 编辑器。
③ 安装 apache2。
④ 容器启动时运行 bash。
构建过程如下图所示:

可以看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。
为什么 Docker 镜像要采用这种分层结构呢?
最大的一个好处就是 - 共享资源。
比如:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享,我们将在后面更深入地讨论这个特性。
这时可能就有人会问了:如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc 下的文件,这时其他容器的 /etc 是否也会被修改?
答案是不会!修改会被限制在单个容器内。这就是我们接下来要学习的容器 Copy-on-Write 特性。
可写的容器层
当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作 "容器层","容器层" 之下的都叫 "镜像层"。

所有对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的。
下面我们深入讨论容器层的细节。
镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。
对容器增删改查操作如下:
| 操作 | 具体执行 |
|---|---|
| 创建 文件 | 新文件只能被添加在容器层中。 |
| 删除 文件 | 依据容器分层结构由上往下依次查找。找到后,在容器层中记录该删除操作。具体实现是,UnionFS 会在容器层创建一个 "whiteout" 文件,将被删除的文件 "遮挡" 起来。 |
| 修改 文件 | 依据容器分层结构由上往下依次查找。找到后,将镜像层中的数据复制到容器层进行修改,修改后的数据保存在容器层中。(copy-on-write) |
| 读取 文件 | 依据容器分层结构由上往下依次查找。 |
只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。
这样就解释了我们前面提出的问题:容器层记录对镜像的修改,所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享。
理解了镜像的原理和结构,下一节我们学习如何构建镜像。
3.4 构建镜像
对于 Docker 用户来说,最好的情况是不需要自己创建镜像。几乎所有常用的数据库、中间件、应用软件等都有现成的 Docker 官方镜像或其他人和组织创建的镜像,我们只需要稍作配置就可以直接使用。
使用现成镜像的好处除了省去自己做镜像的工作量外,更重要的是可以利用前人的经验。特别是使用那些官方镜像,因为 Docker 的工程师知道如何更好的在容器中运行软件。
当然,某些情况下我们也不得不自己构建镜像,比如:
- 找不到现成的镜像,比如自己开发的应用程序。
- 需要在镜像中加入特定的功能,比如官方镜像几乎都不提供 ssh。
所以本节我们将介绍构建镜像的方法。同时分析构建的过程也能够加深我们对前面镜像分层结构的理解。
Docker 容器文件系统
描述:从下面的图片可以看见出以下几点:
- Docker 镜像代表了容器的文件系统里的内容,是容器的基础,镜像一般是通过 Dockerfile 生成的;
- Docker 的镜像是分层的,所有的镜像 (除了基础镜像) 都是在之前镜像的基础上加上自己这层的内容生成的;
- Docker 中每一层镜像的元数据都是存在 json 文件中的,除了静态的文件系统之外,还会包含动态的数据;
- Docker 镜像生成容器后会在此基础之上加入挂载点到安装 Docker 宿主机文件系统之中,并提供一个读写层 (Read-Write Layer),所以容器进程的所有操作都在读写层进行;

Docker 提供了两种构建镜像的方法
- docker commit 命令
- Dockerfile 构建文件
docker commit
docker commit 命令是创建新镜像最直观的方法,其过程包含三个步骤:
- 运行容器
- 修改容器
- 将容器保存为新的镜像
举个例子:在 ubuntu base 镜像中安装 vim 并保存为新镜像。
第一步,运行容器
[root@docker ~]# docker run -it ubuntu
root@8dbdff6d3d88:/#
-it 参数的作用是以交互模式进入容器,并打开终端。8dbdff6d3d88 是容器的内部 ID。
第二步,安装 vim
确认 vim 没有安装:
root@8dbdff6d3d88:/# vim
bash: vim: command not found
安装 vim:
root@8dbdff6d3d88:/# apt-get update
root@8dbdff6d3d88:/# apt-get install -y vim
安装过程中可能会出现时区选择,按提示操作即可。
第三步,保存为新镜像
打开一个新窗口中查看当前运行的容器:
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8dbdff6d3d88 ubuntu "/bin/bash" 2 minutes ago Up 2 minutes cool_darwin
上面查看结果的解释如下:
- 8dbdff6d3d88 是新创建容器的 ID
- cool_darwin 是 Docker 为我们的容器随机分配的名字
执行 docker commit 命令将容器保存为镜像:
[root@docker ~]# docker commit cool_darwin ubuntu-with-vim
# cool_darwin 是容器名,ubuntu-with-vim 是新建的镜像名
sha256:ba18ae460c068f9bdba060350e64dcec4bc4af05b9918602ee34e6350d0369a4
新镜像命名为 ubuntu-with-vim。
查看新镜像的属性:
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim latest acefd029083b 27 minutes ago 189MB
ubuntu latest edbfe74c41f8 5 weeks ago 78.1MB
从 size 上看到镜像因为安装了软件而变大了。
从新镜像启动容器,验证 vim 已经可以使用:
[root@docker ~]# docker run -it ubuntu-with-vim
root@4d071cf3014f:/# which vim
/usr/bin/vim
root@4d071cf3014f:/# vim file1
以上演示了如何用 docker commit 创建新镜像。然而,Docker 并不建议用户通过这种方式构建镜像。原因如下:
- 这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 debian base 镜像中也加入 vim,还得重复前面的所有步骤。
- 更重要的:使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。
既然 docker commit 不是推荐的方法,我们干嘛还要花时间学习呢?
原因是:即便是用 Dockerfile (推荐方法) 构建镜像,底层也是通过 docker commit 一层一层构建新镜像的。学习 docker commit 能够帮助我们更加深入地理解构建过程和镜像的分层结构。
下一节我们学习如何通过 Dockerfile 构建镜像。
3.5 Dockerfile 构建镜像
Dockerfile 是一个文本文件,记录了镜像构建的所有步骤。
Dockerfile 内容基础知识
-
每条保留字指令都必须为大写字母且后面要跟随至少一个参数
-
指令按照从上到下,顺序执行
-
#表示注释
-
每条指令都会创建一个新的镜像层并对镜像进行提交
常用参数
docker build -f [Dockerfile路径] [构建上下文路径]
| 参数 | 作用 |
|---|---|
| -f 或 --file | 标志符,声明要使用自定义 Dockerfile |
| [Dockerfile 路径] | 绝对路径 或 相对于构建上下文的路径 (如 subdir/Dockerfile.dev) |
| [构建上下文路径] | Docker 打包发送给守护进程的目录 (通常用。表示当前目录) |
第一个 Dockerfile
用 Dockerfile 创建上节的 ubuntu-with-vim,其内容为:
FROM ubuntu
RUN apt-get update && apt-get install -y vim
下面我们运行 docker build 命令构建镜像并详细分析每个细节:

[root@docker ~]# cd /root
[root@docker ~]# vim Dockerfile
# 写入上面的 Dockerfile 内容
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile .
① 当前目录为 /root。
② Dockerfile 准备就绪。
③ 运行 docker build 命令,-t 将新镜像命名为 ubuntu-with-vim-dockerfile,命令末尾的。指明 build context 为当前目录。Docker 默认会从 build context 中查找 Dockerfile 文件,我们也可以通过 -f 参数指定 Dockerfile 的位置。
④ 从这步开始就是镜像真正的构建过程。首先 Docker 将 build context 中的所有文件发送给 Docker daemon。build context 为镜像构建提供所需要的文件或目录。
⑤ Dockerfile 中的 ADD、COPY 等命令可以将 build context 中的文件添加到镜像。此例中,build context 为当前目录 /root,该目录下的所有文件和子目录都会被发送给 Docker daemon。
⑥ 所以,使用 build context 就得小心了,不要将多余文件放到 build context,特别不要把 / 、/usr 作为 build context,否则构建过程会相当缓慢甚至失败。
⑦ Step 1: 执行 FROM,将 ubuntu 作为 base 镜像。
⑧ Step 2: 执行 RUN,安装 vim。
⑨ 镜像构建成功。
⑩ 镜像重命名为 ubuntu-with-vim-dockerfile。
通过 docker images 查看镜像信息:
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim latest acefd029083b 27 minutes ago 189MB
ubuntu-with-vim-dockerfile latest bbc08145d011 10 minutes ago 189MB
ubuntu latest edbfe74c41f8 5 weeks ago 78.1MB
镜像 ID 为 bbc08145d011,与构建时的输出一致。
在上面的构建过程中,我们要特别注意指令 RUN 的执行过程。Docker 会在启动的临时容器中执行操作,并通过 commit 保存为新的镜像。
查看镜像分层结构
ubuntu-with-vim-dockerfile 是通过在 base 镜像的顶部添加一个新的镜像层而得到的。

这个新镜像层的内容由 RUN apt-get update && apt-get install -y vim 生成。这一点我们可以通过 docker history 命令验证:

docker history 会显示镜像的构建历史,也就是 Dockerfile 的执行过程。
ubuntu-with-vim-dockerfile 与 ubuntu 镜像相比,确实只是多了顶部的一层 bbc08145d011,由 apt-get 命令创建,大小为 111MB。docker history 也向我们展示了镜像的分层结构,每一层由上至下排列。
注: 表示无法获取 IMAGE ID,通常从 Docker Hub 下载的镜像会有这个问题。
下一节我们学习镜像的缓存特性。
3.6 镜像的缓存特性
上一节我们学习了镜像的分层结构,接下来讨论镜像的缓存特性。
Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无需重新创建。
举例说明
在前面的 Dockerfile 中添加一点新内容,往镜像中复制一个文件:

执行构建:
[root@docker ~]# pwd
/root
[root@docker ~]# touch testfile
[root@docker ~]# ls
Dockerfile testfile
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile-2 .
① 确保 testfile 已存在。
② 重点:之前已经运行过相同的 RUN 指令,这次直接使用缓存中的镜像层。
③ 执行 COPY 指令:启动临时容器,复制 testfile,提交新的镜像层 5561217926be,删除临时容器。
在 ubuntu-with-vim-dockerfile 镜像上直接添加一层就得到了新的镜像 ubuntu-with-vim-dockerfile-2。

如果我们希望在构建镜像时不使用缓存,可以在 docker build 命令中加上 --no-cache 参数。
缓存失效场景
Dockerfile 中每一个指令都会创建一个镜像层,上层是依赖于下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。
也就是说,如果我们改变 Dockerfile 指令的执行顺序,或者修改或添加指令,都会使缓存失效。
举例说明
交换前面 RUN 和 COPY 的顺序:

构建新镜像:
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile-3 .
从输出可以看到 [2/3]、[3/3] 都没有使用缓存,最后生成了新的镜像层 33f20b2ec8fd,缓存已经失效。

下载镜像时的缓存
除了构建时使用缓存,Docker 在下载镜像时也会使用。例如我们下载 httpd 镜像:

root@ubuntu:~# docker pull httpd
Using default tag: latest
latest: Pulling from library/httpd
386a066cd84a: Already exists
a11d6b8e2fac: Pull complete
c1fdc7beec37: Pull complete
bd14a67deca2: Pull complete
92b34ad02810: Pull complete
Digest: sha256:5b4a3c85b4b874e84174ee7e78a59920818aa39903f6a28a47b9278f576b4a4d
Status: Downloaded newer image for httpd:latest
docker pull 命令输出显示第一层 (base 镜像) 已经存在,不需要下载。由 Dockerfile 可知 httpd 的 base 镜像为 debian,正好之前已经下载过 debian 镜像,所以有缓存可用。通过 docker history 可以进一步验证。

3.7 调试 Dockerfile
包括 Dockerfile 在内的任何脚本和程序都会出错。有错并不可怕,但必须有办法排查,所以本节讨论如何 debug Dockerfile。
构建镜像的过程回顾
- 从 base 镜像运行一个容器。
- 执行一条指令,对容器做修改。
- 执行类似 docker commit 的操作,生成一个新的镜像层。
- Docker 再基于刚刚提交的镜像运行一个新容器。
- 重复 2-4 步,直到 Dockerfile 中的所有指令执行完毕。
从这个过程可以看出,如果 Dockerfile 由于某种原因执行到某个指令失败了,我们也将能够得到前一个指令成功执行构建出的镜像,这对调试 Dockerfile 非常有帮助。我们可以运行最新的这个镜像定位指令失败的原因。
调试例子
Dockerfile 内容如下:

执行 docker build:
[root@docker ~]# ls
Dockerfile testfile
[root@docker ~]# docker build -t image-debug .
Dockerfile 在执行第三步 RUN 指令时失败。我们可以利用 busybox 的镜像进行调试,方式是通过 docker run -it 启动镜像的一个容器:

[root@docker ~]# docker run -it busybox /bin/sh
/ # /bin/bash -c "echo continue to build..."
sh: /bin/bash: not found
/ # /bin/sh -c "echo continue to build..."
continue to build...
手工执行 RUN 指令很容易定位失败的原因是 busybox 镜像中没有 bash,busybox 中用的是 sh。
修复错误
修改 Dockerfile:
FROM busybox
RUN touch tmpfile
RUN /bin/sh -c "echo continue to build..."
COPY testfile /
重新构建:
[root@docker ~]# docker build -t image-debug .
构建成功!

到这里相信大家对 Dockerfile 的功能和使用流程有了比较完整的印象,但还没有系统学习 Dockerfile 的各种指令和实际用法,下节会开始这个主题。
3.8 Dockerfile 常用指令
是时候系统学习 Dockerfile 了。
下面列出了 Dockerfile 中最常用的指令,完整列表和说明可参看官方文档。
FROM
指定 base 镜像。第一条必须是 FROM。
MAINTAINER
设置镜像的作者,可以是任意字符串。
COPY
将文件从 build context 复制到镜像。
COPY 支持两种形式:
- COPY src dest
- COPY ["src", "dest"]
注意:src 只能指定 build context 中的文件或目录。
ADD
与 COPY 类似,从 build context 复制文件到镜像。不同的是,如果 src 是归档文件 (tar, zip, tgz, xz 等),文件会被自动解压到 dest。
ENV
设置环境变量,环境变量可被后面的指令使用。例如:
ENV MY_VERSION 1.3
RUN apt-get install -y mypackage=$MY_VERSION
EXPOSE
指定容器中的进程会监听某个端口,Docker 可以将该端口暴露出来。我们会在容器网络部分详细讨论。
VOLUME
将文件或目录声明为 volume。我们会在容器存储部分详细讨论。
WORKDIR
为后面的 RUN、CMD、ENTRYPOINT、ADD 或 COPY 指令设置镜像中的当前工作目录。
RUN
在容器中运行指定的命令。
CMD
容器启动时运行指定的命令。
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。
ENTRYPOINT
设置容器启动时运行的命令。
Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT。
较为全面的 Dockerfile 示例
# my dockerfile
FROM busybox
MAINTAINER 6946630@qq.com
WORKDIR /testdir
RUN touch tmpfile1
COPY ["tmpfile2", "."]
ADD ["passwd.tar.gz", "."]
ENV WELCOME "You are in my container, welcome!"
完整的操作步骤
[root@docker ~]# pwd
/root
[root@docker ~]# touch tmpfile2
[root@docker ~]# cp /etc/passwd .
[root@docker ~]# tar -cvzf passwd.tar.gz passwd
passwd
[root@docker ~]# rm passwd
rm: remove regular file 'passwd'? y
[root@docker ~]# vim Dockerfile
# 写入上面的 Dockerfile 内容
[root@docker ~]# ls
Dockerfile passwd.tar.gz tmpfile2
[root@docker ~]# docker build -t my-image .

验证镜像内容
[root@docker ~]# docker run -it my-image
/testdir # pwd
/testdir
/testdir # ls
passwd tmpfile1 tmpfile2
/testdir # echo $WELCOME
You are in my container, welcome!
① 进入容器,当前目录即为 WORKDIR。如果 WORKDIR 不存在,Docker 会自动为我们创建。
② WORKDIR 中保存了我们希望的文件和目录:
-
文件 passwd:由 ADD 指令从 build context 复制的归档文件 passwd.tar.gz,已经自动解压。
-
文件 tmpfile1:由 RUN 指令创建。
-
文件 tmpfile2:由 COPY 指令从 build context 复制。

③ ENV 指令定义的环境变量已经生效。
在上面这些指令中,RUN、CMD、ENTRYPOINT 很重要且容易混淆,下节专门讨论。
3.9 RUN vs CMD vs ENTRYPOINT
RUN、CMD 和 ENTRYPOINT 这三个 Dockerfile 指令看上去很类似,很容易混淆。本节将通过实践详细讨论它们的区别。
简单的说:
- RUN 执行命令并创建新的镜像层,RUN 经常用于安装软件包。
- CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被 docker run 后面跟的命令行参数替换。
- ENTRYPOINT 配置容器启动时运行的命令。
下面我们详细分析。
Shell 和 Exec 格式
我们可用两种方式指定 RUN、CMD 和 ENTRYPOINT 要运行的命令:Shell 格式和 Exec 格式,二者在使用上有细微的区别。
Shell 格式
<<instruction> <command>
例如:
RUN apt-get install python3
CMD echo "Hello world"
ENTRYPOINT echo "Hello world"
当指令执行时,shell 格式底层会调用 /bin/sh -c 。
例如下面的 Dockerfile:
FROM busybox
ENV name gqd
ENTRYPOINT echo "Hello, $name"
构建并运行:
[root@docker ~]# docker build -t dockerfile1 .
[root@docker ~]# docker run dockerfile1
Hello, gqd
注意环境变量 name 已经被值 gqd 替换。
Exec 格式
<<instruction> ["executable", "param1", "param2", ...]
例如:
RUN ["apt-get", "install", "python3"]
CMD ["/bin/echo", "Hello world"]
ENTRYPOINT ["/bin/echo", "Hello world"]
当指令执行时,会直接调用 executable,不会被 shell 解析。
例如下面的 Dockerfile:
FROM busybox
ENV name gqd
ENTRYPOINT ["/bin/echo", "Hello, $name"]
构建并运行:
[root@docker ~]# docker build -t dockerfile2 .
[root@docker ~]# docker run dockerfile2
Hello, $name
注意环境变量 "name" 没有被替换。
如果希望使用环境变量,照如下修改:
FROM busybox
ENV name gqd
ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]
构建并运行:
[root@docker ~]# docker build -t dockerfile3 .
[root@docker ~]# docker run dockerfile3
Hello, gqd
CMD 和 ENTRYPOINT 推荐使用 Exec 格式,因为指令可读性更强,更容易理解。RUN 则两种格式都可以。
RUN
RUN 指令通常用于安装应用和软件包。
RUN 在当前镜像的顶部执行命令,并通过创建新的镜像层。Dockerfile 中常常包含多个 RUN 指令。
RUN 有两种格式:
- Shell 格式:RUN
- Exec 格式:RUN ["executable", "param1", "param2"]
示例:安装多个包
FROM ubuntu
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
构建并验证:
[root@docker ~]# docker build -t dockerfile4 .
[root@docker ~]# docker run -it dockerfile4
root@43894b9f29db:/# apt list --installed | grep -E "bzr|cvs|git|mercurial|subversion"
注意:apt-get update 和 apt-get install 被放在一个 RUN 指令中执行,这样能够保证每次安装的是最新的包。如果 apt-get install 在单独的 RUN 中执行,则会使用 apt-get update 创建的镜像层,而这一层可能是很久以前缓存的。
CMD
CMD 指令允许用户指定容器的默认执行的命令。
此命令会在容器启动且 docker run 没有指定其他命令时运行。
- 如果 docker run 指定了其他命令,CMD 指定的默认命令将被忽略。
- 如果 Dockerfile 中有多个 CMD 指令,只有最后一个 CMD 有效。
CMD 有三种格式:
- Exec 格式:CMD ["executable","param1","param2"] 这是 CMD 的推荐格式。
- CMD ["param1","param2"] 为 ENTRYPOINT 提供额外的参数,此时 ENTRYPOINT 必须使用 Exec 格式。
- Shell 格式:CMD command param1 param2
示例
Dockerfile 如下:
FROM busybox
CMD echo "Hello, world"
构建并运行:
[root@docker ~]# docker build -t dockerfile5 .
[root@docker ~]# docker run -it dockerfile5
Hello, world
当后面加上一个命令,比如 docker run -it dockerfile5 /bin/sh,CMD 会被忽略掉,命令 sh 将被执行:
[root@docker ~]# docker run -it dockerfile5 /bin/sh
/ #
ENTRYPOINT
ENTRYPOINT 指令可让容器以应用程序或者服务的形式运行。
ENTRYPOINT 看上去与 CMD 很像,它们都可以指定要执行的命令及其参数。不同的地方在于 ENTRYPOINT 不会被忽略,一定会被执行,即使运行 docker run 时指定了其他命令。
ENTRYPOINT 有两种格式:
- Exec 格式:ENTRYPOINT ["executable", "param1", "param2"] 这是 ENTRYPOINT 的推荐格式。
- Shell 格式:ENTRYPOINT command param1 param2
在为 ENTRYPOINT 选择格式时必须小心,因为这两种格式的效果差别很大。
Exec 格式
ENTRYPOINT 的 Exec 格式用于设置要执行的命令及其参数,同时可通过 CMD 提供额外的参数。
ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉。
比如下面的 Dockerfile:
FROM busybox
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
构建并运行:
[root@docker ~]# docker build -t dockerfile6 .
[root@docker ~]# docker run -it dockerfile6
Hello world
而如果通过 docker run -it dockerfile6 gqd 启动,则输出为:
[root@docker ~]# docker run -it dockerfile6 gqd
Hello gqd
Shell 格式
ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 docker run 提供的参数。
比如下面的 Dockerfile:
FROM busybox
ENTRYPOINT echo "Hello,"
CMD ["world"]
构建并运行:
[root@docker ~]# docker build -t dockerfile7 .
[root@docker ~]# docker run -it dockerfile7
Hello,
而如果通过 docker run -it dockerfile7 gqd 启动,则输出为:
[root@docker ~]# docker run -it dockerfile7 gqd
Hello,
最佳实践
- 使用 RUN 指令安装应用和软件包,构建镜像。
- 如果 Docker 镜像的用途是运行应用程序或服务,比如运行一个 MySQL,应该优先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可为 ENTRYPOINT 提供额外的默认参数,同时可利用 docker run 命令行替换默认参数。
- 如果想为容器设置默认的启动命令,可使用 CMD 指令。用户可在 docker run 命令行中替换此默认命令。
到这里,我们已经具备编写 Dockerfile 的能力了。如果大家还觉得没把握,推荐一个快速掌握 Dockerfile 的方法:去 Docker Hub 上参考那些官方镜像的 Dockerfile。
好了,我们已经学习完如何创建自己的 image,下一节讨论如何分发 image。
Dockerfile 案例:配置 SSH 镜像
项目背景:官方下载的 centos 镜像默认不带 ssh,管理起来不方便,自己制作一个带 SSH 功能的 centos 镜像。
创建 Dockerfile
FROM centos:8.4.2105
MAINTAINER gaoqiaodong
RUN minorver=8.4.2105 && \
sed -e "s|^mirrorlist=|#mirrorlist=|g" \
-e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" \
-i.bak \
/etc/yum.repos.d/CentOS-*.repo
RUN yum install -y openssh-server
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
RUN echo "root:huawei" | chpasswd
EXPOSE 22
CMD ["/usr/sbin/sshd","-D"]
构建镜像
[root@docker ~]# docker build -t centos:ssh -f centos.ssh.dockerfile .
查看镜像历史
[root@docker ~]# docker history centos:ssh
测试
# 基于镜像创建容器 sshtest
[root@docker ~]# docker run -d -p 2022:22 --name sshtest centos:ssh
73d963d15407a1e73097540bb320b9edf05b468001bd707abf01bc7be5e54bcb
# 查看容器
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
73d963d15407 centos:ssh "/usr/sbin/sshd -D" 6 seconds ago Up 5 seconds 0.0.0.0:2022->22/tcp, :::2022->22/tcp sshtest
# SSH 登录容器测试
[root@docker ~]# ssh root@localhost -p 2022
The authenticity of host '[localhost]:2022 ([::1]:2022)' can't be established.
ECDSA key fingerprint is SHA256:z1owYLOuClnbPrZwXxgy1jcItQT1k+QX6LxosydT64A.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts.
root@localhost's password:
"System is booting up. Unprivileged users are not permitted to log in yet. Please come back later. For technical details, see pam_nologin(8)."
[root@73d963d15407 ~]#
Dockerfile 案例:自定义 httpd 镜像
创建 Dockerfile
FROM centos:8.4.2105
MAINTAINER gaoqiaodong
RUN minorver=8.4.2105 \
&& sed -e "s|^mirrorlist=|#mirrorlist=|g" -e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" -i.bak /etc/yum.repos.d/CentOS-*.repo
RUN yum install -y httpd && yum clean all && rm -rf /var/cache/yum
COPY index.html /var/www/html/
EXPOSE 80
CMD ["/usr/sbin/httpd", "-DFOREGROUND"]
创建 index.html:
[root@docker ~]# echo Hello World > index.html
构建镜像
[root@docker ~]# docker build -t httpd:centos -f httpd.dockerfile .
查看镜像历史
[root@docker ~]# docker history httpd:centos
测试
# 基于镜像创建容器 myweb
[root@docker ~]# docker run -d -p 80:80 --name myweb httpd:centos
1e0b0631cf708bcc0a162d56b936a12cd06c9bcdebcc2594b23c2fbee3ed8894
# 查看容器
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e0b0631cf70 httpd:centos "/usr/sbin/httpd -DF..." About a minute ago Up About a minute 0.0.0.0:80->80/tcp, :::80->80/tcp myweb
# 访问测试
[root@docker ~]# curl localhost
Hello World
Dockerfile 案例:自定义 mycentosjava8
项目背景:java 工程师需要带 JAVA 的 centos,要求 CentOS8 镜像具备 vim+ifconfig+jdk8。
JDK 的下载地址: https://www.oracle.com/java/technologies/downloads/#java8

编写 Dockerfile 文件
FROM centos:8.4.2105
MAINTAINER gaoqiaodong<6946630@qq.com>
ENV MYPATH /usr/local
WORKDIR $MYPATH
# 配置yum源
RUN minorver=8.4.2105 \
&& sed -e "s|^mirrorlist=|#mirrorlist=|g" -e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" -i.bak /etc/yum.repos.d/CentOS-*.repo
# 安装vim编辑器
RUN yum -y install vim
# 安装ifconfig命令查看网络IP
RUN yum -y install net-tools
# 安装java8及lib库
RUN yum -y install glibc.i686
RUN mkdir /usr/local/java
# ADD 是相对路径jar,把jdk-8u461-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-8u461-linux-x64.tar.gz /usr/local/java/
# 配置java环境变量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_461
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
EXPOSE 80
CMD echo $MYPATH
CMD echo "success--------------ok"
CMD /bin/bash
将 jdk-8u461-linux-x64.tar.gz 与 Dockerfile 放到同一目录:
[root@docker myfile]# ls
Dockerfile jdk-8u461-linux-x64.tar.gz
构建镜像
[root@docker myfile]# docker build -t centosjava8:461 .
查看构建的镜像
[root@docker myfile]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centosjava8 461 8097b762c0a5 4 minutes ago 537MB
测试容器
[root@docker myfile]# docker run -it centosjava8:461 /bin/bash
[root@6b2767bcdf24 local]# pwd
/usr/local
[root@6b2767bcdf24 local]# java -version
java version "1.8.0_461"
Java(TM) SE Runtime Environment (build 1.8.0_461-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.461-b11, mixed mode)
[root@6b2767bcdf24 local]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.3 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:03 txqueuelen 0 (Ethernet)
RX packets 8 bytes 656 (656.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@6b2767bcdf24 local]# vim file1
3.10 镜像命名的最佳实践
我们已经学会构建自己的镜像了。接下来的问题是如何在多个 Docker Host 上使用镜像。
这里有几种可用的方法:
- 用相同的 Dockerfile 在其他 host 构建镜像。
- 将镜像上传到公共 Registry (比如 Docker Hub),Host 直接下载使用。
- 搭建私有的 Registry 供本地 Host 使用。
第一种方法没什么特别的,前面已经讨论很多了。我们将讨论如何使用公共和私有 Registry 分发镜像。
为镜像命名
无论采用何种方式保存和分发镜像,首先都得给镜像命名。
当我们执行 docker build 命令时已经为镜像取了个名字,例如前面:
docker build -t ubuntu-with-vim .
这里的 ubuntu-with-vim 就是镜像的名字。通过 docker images 可以查看镜像的信息:
[root@docker ~]# docker images ubuntu-with-vim
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim latest 9ae1560698f0 40 seconds ago 189MB
这里注意到 ubuntu-with-vim 对应的是 REPOSITORY,而且还有一个叫 latest 的 TAG。
实际上一个特定镜像的名字由两部分组成:repository 和 tag。
[image name] = [repository]:[tag]
全称如下:
镜像名称格式: Image hub address/Namespace/Repository:tag
即 054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com/library/nginx:latest
如果执行 docker build 时没有指定 tag,会使用默认值 latest。其效果相当于:
docker build -t ubuntu-with-vim:latest .
tag 常用于描述镜像的版本信息,比如 httpd 镜像:

当然 tag 可以是任意字符串,比如 ubuntu 镜像:

小心 latest tag
千万别被 latest tag 给误导了。latest 其实并没有什么特殊的含义。当没指明镜像 tag 时,Docker 会使用默认值 latest,仅此而已。
虽然 Docker Hub 上很多 repository 将 latest 作为最新稳定版本的别名,但这只是一种约定,而不是强制规定。
所以我们在使用镜像时最好还是避免使用 latest,明确指定某个 tag,比如 httpd:2.4,ubuntu:xenial。
tag 使用最佳实践
借鉴软件版本命名方式能够让用户很好地使用镜像。
一个高效的版本命名方案可以让用户清楚地知道当前使用的是哪个镜像,同时还可以保持足够的灵活性。
每个 repository 可以有多个 tag,而多个 tag 可能对应的是同一个镜像。下面通过例子为大家介绍 Docker 社区普遍使用的 tag 方案。
假设我们现在发布了一个镜像 myimage,版本为 v1.9.1。那么我们可以给镜像打上四个 tag:1.9.1、1.9、1 和 latest。
:latest
:1
:1.9
:1.9.1 V1.9.1
我们可以通过 docker tag 命令方便地给镜像打 tag:
docker tag myimage-v1.9.1 myimage:1
docker tag myimage-v1.9.1 myimage:1.9
docker tag myimage-v1.9.1 myimage:1.9.1
docker tag myimage-v1.9.1 myimage:latest
过了一段时间,我们发布了 v1.9.2。这时可以打上 1.9.2 的 tag,并将 1.9、1 和 latest 从 v1.9.1 移到 v1.9.2:
docker tag myimage-v1.9.2 myimage:1
docker tag myimage-v1.9.2 myimage:1.9
docker tag myimage-v1.9.2 myimage:1.9.2
docker tag myimage-v1.9.2 myimage:latest
之后,v2.0.0 发布了。这时可以打上 2.0.0、2.0 和 2 的 tag,并将 latest 移到 v2.0.0:
docker tag myimage-v2.0.0 myimage:2
docker tag myimage-v2.0.0 myimage:2.0
docker tag myimage-v2.0.0 myimage:2.0.0
docker tag myimage-v2.0.0 myimage:latest
这种 tag 方案使镜像的版本很直观,用户在选择非常灵活:
- myimage:1 始终指向 1 这个分支中最新的镜像。
- myimage:1.9 始终指向 1.9.x 中最新的镜像。
- myimage:latest 始终指向所有版本中最新的镜像。
- 如果想使用特定版本,可以选择 myimage:1.9.1、myimage:1.9.2 或 myimage:2.0.0。
Docker Hub 上很多 repository 都采用这种方案,所以大家一定要熟悉。
下一节讨论如何使用公共 Registry。
3.11 使用公共 Registry-dockerhub
Docker 仓库类似于代码仓库,它是 Docker 集中存放镜像文件的场所。仓库注册服务器存放着很多类镜像,每类镜像包括多个镜像文件,通过不同的标签 (tag) 来进行区分。
根据所存储的镜像公开分享与否,Docker 仓库可以分为:
- 公开仓库 (Public)
- 私有仓库 (Private)
目前,最大的公开仓库是官方提供的 Docker Hub,其中存放了数量庞大的镜像供用户下载。国内不少云服务提供商 (如华为、阿里云等) 也提供了仓库的本地源,可以提供稳定的国内访问。
当然,用户如果不希望公开分享自己的镜像文件,Docker 也支持用户在本地网络内创建一个只能自己访问的私有仓库。当用户创建了自己的镜像之后就可以使用 push 命令将它上传到指定的公有或者私有仓库。这样用户下次在另外一台机器上使用该镜像时,只需要将其从仓库上 pull 下来就可以了。
保存和分发镜像的最直接方法就是使用 Docker Hub。
Docker Hub 是 Docker 公司维护的公共 Registry。用户可以将自己的镜像保存到 Docker Hub 免费的 repository 中。如果不希望别人访问自己的镜像,也可以购买私有 repository。
除了 Docker Hub,quay.io 是另一个公共 Registry,提供与 Docker Hub 类似的服务。
下面介绍如何用 Docker Hub 存取我们的镜像。
步骤 1:注册 Docker Hub 账号
访问 https://hub.docker.com/ 注册账号。
步骤 2:在 Docker Host 上登录
[root@docker ~]# docker login -u gaoqd
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
步骤 3:修改镜像的 repository 使之与 Docker Hub 账号匹配
Docker Hub 为了区分不同用户的同名镜像,镜像的 registry 中要包含用户名,完整格式为:
[username]/xxx:tag
我们通过 docker tag 命令重命名镜像:
[root@docker ~]# docker tag httpd gaoqd/httpd:v1
[root@docker ~]# docker images gaoqd/httpd
REPOSITORY TAG IMAGE ID CREATED SIZE
gaoqd/httpd v1 9cb0a2315602 8 weeks ago 148MB
注:Docker 官方自己维护的镜像没有用户名,比如 httpd。
步骤 4:通过 docker push 将镜像上传到 Docker Hub
[root@docker ~]# docker push gaoqd/httpd:v1
Docker 会上传镜像的每一层。因为 gaoqd/httpd:v1 这个镜像实际上跟官方的 httpd 镜像一模一样,Docker Hub 上已经有了全部的镜像层,所以真正上传的数据很少。
如果想上传同一 repository 中所有镜像,省略 tag 部分就可以了:
docker push gaoqd/httpd
步骤 5:查看上传的镜像
登录 https://hub.docker.com,在 Public Repository 中就可以看到上传的镜像。

如果要删除上传的镜像,只能在 Docker Hub 界面上操作。
步骤 6:下载镜像
这个镜像可被其他 Docker host 下载使用:
[root@docker ~]# docker pull gaoqd/httpd:v1
v1: Pulling from gaoqd/httpd
a2318d6c47ec: Pull complete
62dd86107c65: Pull complete
4f4fb700ef54: Pull complete
22871f73faed: Pull complete
ca061a523d1f: Pull complete
509789394c2a: Pull complete
Digest: sha256:9a1f89a470bc5ec4c5dc9e1aa13bd20898d59b71890cf27098507fc39750d988
Status: Downloaded newer image for gaoqd/httpd:v1
docker.io/gaoqd/httpd:v1
使用公共 Registry - 华为云
步骤 1:登录华为云
访问 https://www.huaweicloud.com 登录账号。
步骤 2:进入容器镜像服务 SWR
访问 https://console.huaweicloud.com/swr/,选择区域,创建组织。
步骤 3:获取登录指令
在华为云 SWR 控制台的 "新手入门" 或 "登录指令" 中,复制通用型登录指令(包含用户名、密码和仓库地址)。
步骤 4:在 Docker Host 上登录华为云 SWR
[root@docker ~]# docker login -u cn-east-3@HST3WXW3A49Z51MDDYQ9 -p 92b18fd358e36a7cd6d3c8bf5a46ab9570e8498311b28ea1adc08b816bbf6336 swr.cn-east-3.myhuaweicloud.com
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
步骤 5:上传镜像
命令格式:
sudo docker tag {镜像名称}:{版本名称} swr.cn-east-3.myhuaweicloud.com/{组织名称}/{镜像名称}:{版本名称}
sudo docker push swr.cn-east-3.myhuaweicloud.com/{组织名称}/{镜像名称}:{版本名称}
示例:上传之前构建的 httpd:centos 镜像
[root@docker ~]# docker images httpd:centos
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd centos 973cac3a687f 4 hours ago 288MB
# 修改镜像名
[root@docker ~]# docker tag httpd:centos swr.cn-east-3.myhuaweicloud.com/gaoqiaodong/centos_httpd:v1
# 上传镜像
[root@docker ~]# docker push swr.cn-east-3.myhuaweicloud.com/gaoqiaodong/centos_httpd:v1
步骤 6:查看上传的镜像
登录华为云 SWR 控制台,进入创建的组织,即可看到上传的镜像。

步骤 7:下载镜像
在其他 Docker Host 上登录华为云 SWR 后,执行 pull 命令下载:
[root@docker ~]# docker pull swr.cn-east-3.myhuaweicloud.com/gaoqiaodong/centos_httpd:v1
3.12 搭建本地 Registry
Docker Hub 虽然非常方便,但还是有些限制,比如:
- 需要 internet 连接,而且下载和上传速度慢。
- 上传到 Docker Hub 的镜像任何人都能够访问,虽然可以用私有 repository,但不是免费的。
- 安全原因很多组织不允许将镜像放到外网。
解决方案就是搭建本地的 Registry。
Registry
Docker 已经将 Registry 开源了,同时在 Docker Hub 上也有官方的镜像 registry。下面我们就在 Docker 中运行自己的 registry。
-
启动 registry 容器。
[root@docker ~]# docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry
registry:2
Unable to find image 'registry:2' locally
2: Pulling from library/registry
1cc3d825d8b2: Pull complete
85ab09421e5a: Pull complete
40960af72c1c: Pull complete
e7bb1dbb377e: Pull complete
a538cc9b1ae3: Pull complete
Digest:
sha256:ac0192b549007e22998eb74e8d8488dcfe70f1489520c3b144a6047ac5efbe90
Status: Downloaded newer image for registry:2
a8214201171f28a13803c647ef84acdc982465c230cb5c6a034bd05574948408
[root@docker ~]#
我们使用的镜像是 registry:2。
-d 是后台启动容器。
-p 将容器的 5000 端口映射到 Host 的 5000 端口。5000 是 registry 服务端口。端口映射我们会 在容器网络章节详细讨论。
-v 将容器 /var/lib/registry 目录映射到 Host 的 /myregistry,用于存放镜像数据。-v 的使用我们 会在容器存储章节详细讨论。
通过 docker tag 重命名镜像,使之与 registry 匹配。
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID
CREATED SIZE
httpd latest
148MB
REPOSITORY TAG IMAGE ID
CREATED SIZE
httpd latest
148MB
localhost:5000/httpd v1
148MB
5daf6a4bfe74 2 months ago 148MB
[root@docker ~]# docker tag httpd:latest localhost:5000/httpd:v1
[root@docker ~]# docker images
5daf6a4bfe74 2 months ago 148MB
5daf6a4bfe74 2 months ago 148MB
我们在镜像的前面加上了运行 registry 的主机名称和端口。
前面已经讨论了镜像名称由 repository 和 tag 两部分组成。而 repository 的完整格式为:
只有 Docker Hub 上的镜像可以省略 registry-host:[port] 。
通过 docker push 上传镜像。
[root@docker ~]# docker push localhost:5000/httpd:v1
The push refers to repository [localhost:5000/httpd]
85d0eb049481: Pushed
53a350bcb78a: Pushed
db9328cd0153: Pushed
5f70bf18a086: Pushed
3bbc250aae52: Pushed
8d853c8add5d: Pushed
v1: digest:
sha256:f432c26db81bdb6eb2c60c61d5d607615398f2e983eaaaff87ade6bbb2fec875 size:
1572
[root@docker httpd]# curl http://localhost:5000/v2/_catalog
{"repositories":["httpd"]}
现在已经可通过 docker pull 从本地 registry 下载镜像了。
#下载之前先删除本地的镜像
[root@docker ~]# docker rmi httpd:latest
[root@docker ~]# docker rmi localhost:5000/httpd:v1
#从自建仓库下载镜像
[root@docker ~]# docker pull localhost:5000/httpd:v1
v1: Pulling from httpd
302e3ee49805: Already exists
4669bea11670: Pull complete
4f4fb700ef54: Pull complete
ca8887d72588: Pull complete
c7c900975bf7: Pull complete
95eac36196b2: Pull complete
Digest:
sha256:f432c26db81bdb6eb2c60c61d5d607615398f2e983eaaaff87ade6bbb2fec875
#验证从自建仓库下载的镜像
[root@docker ~]# docker images localhost:5000/httpd
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost:5000/httpd v1 5daf6a4bfe74
v1 5daf6a4bfe74
2 months ago 148MB
148MB
除了镜像的名称长一些 (包含 registry host 和 port),使用方式完全一样。
以上是搭建本地 registry 的简要步骤。当然 registry 也支持认证,https 安全传输等特性,具体可以参考 官方文档 https://docs.docker.com/registry/configuration/
企业级私有仓库 Harbor
我们如果需要部署一个私有镜像仓库来使用,最简单的就是 registry , 一行命令就可以运行在 Docker 中,但功能也比较弱,如果想要私有镜像仓库功能更丰富些,可以使用 Harbor。
本文简单介绍下 Harbor 的安装和使用。
Harbor 是构建企业级私有 docker 镜像的仓库的开源解决方案,它是 Docker Registry 的更高级封装,除了 提供友好的 Web UI 界面,角色和用户权限管理,用户操作审计等功能外,它还整合了 K8s 的插件 (Addons) 仓库。
harbor 下载
下载地址:https://github.com/goharbor/harbor/releases
安装
[root@docker ~]# wget
https://github.com/goharbor/harbor/releases/download/v2.9.1/harbor-offline-
installer-v2.9.1.tgz
如果无法通过 wget 进行下载,可以直接到 Github 网站:https://github.com/goharbor/harbor/releas es/ 进行下载,然后拷贝到服务器中
[root@docker ~]# ls
harbor-offline-installer-v2.9.1.tgz
执行下面命令进行解压
[root@docker ~]# tar -xvf harbor-offline-installer-v2.9.1.tgz
harbor/harbor.v2.9.1.tar.gz
harbor/prepare
harbor/LICENSE
harbor/install.sh
harbor/common.sh
harbor/harbor.yml.tmpl
执行下面命令新建目录,并将程序文件复制到目录中:
[root@docker ~]# mkdir /opt/harbor
[root@docker ~]# mv harbor/* /opt/harbor/
导入 Harbor 镜像
[root@docker ~]# cd /opt/harbor/
[root@docker harbor]# docker load -i harbor.v2.9.1.tar.gz
修改 Harbor 配置文件
[root@docker ~]# cd /opt/harbor/
[root@docker harbor]# cp -ar harbor.yml.tmpl harbor.yml
[root@docker harbor]# vim harbor.yml
配置文件内容修改如下:

hostname: 如果只是内网访问,设置为内网 IP, 如果需要外网访问,就必须设置为外网域名或 IP
https: #注释掉
harbor_admin_password: harbor 登录密码
编辑完配置文件,接下来在 harbor 目录下安装 Harbor。先进行预处理更新配置文件
[root@docker harbor]# ./prepare
prepare base dir is set to /opt/harbor
WARNING:root:WARNING: HTTP protocol is insecure. Harbor will deprecate http
protocol in the future. Please make sure to upgrade to https
Generated configuration file: /config/portal/nginx.conf
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/log/rsyslog_docker.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf
Generated configuration file: /config/registry/config.yml
Generated configuration file: /config/registryctl/env
Generated configuration file: /config/registryctl/config.yml
Generated configuration file: /config/db/env
Generated configuration file: /config/jobservice/env
Generated configuration file: /config/jobservice/config.yml
Generated and saved secret to file: /data/secret/keys/secretkey
Successfully called func: create_root_cert
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir
执行下面命令进行安装
[root@docker harbor]# ./install.sh
[Step 0]: checking if docker is installed ...
Note: docker version: 26.1.3
[Step 1]: checking docker-compose is installed ...
Note: Docker Compose version v2.27.0
[Step 2]: loading Harbor images ...
Loaded image: goharbor/harbor-jobservice:v2.9.1
Loaded image: goharbor/harbor-registryctl:v2.9.1
Loaded image: goharbor/harbor-core:v2.9.1
Loaded image: goharbor/harbor-log:v2.9.1
Loaded image: goharbor/harbor-db:v2.9.1
Loaded image: goharbor/harbor-exporter:v2.9.1
Loaded image: goharbor/redis-photon:v2.9.1
Loaded image: goharbor/nginx-photon:v2.9.1
Loaded image: goharbor/registry-photon:v2.9.1
Loaded image: goharbor/trivy-adapter-photon:v2.9.1
Loaded image: goharbor/prepare:v2.9.1
Loaded image: goharbor/harbor-portal:v2.9.1
[Step 3]: preparing environment ...
[Step 4]: preparing harbor configs ...
prepare base dir is set to /opt/harbor
WARNING:root:WARNING: HTTP protocol is insecure. Harbor will deprecate http
protocol in the future. Please make sure to upgrade to https
Clearing the configuration file: /config/portal/nginx.conf
Clearing the configuration file: /config/log/logrotate.conf
Clearing the configuration file: /config/log/rsyslog_docker.conf
Clearing the configuration file: /config/nginx/nginx.conf
Clearing the configuration file: /config/core/env
Clearing the configuration file: /config/core/app.conf
Clearing the configuration file: /config/registry/passwd
Clearing the configuration file: /config/registry/config.yml
Clearing the configuration file: /config/registryctl/env
Clearing the configuration file: /config/registryctl/config.yml
Clearing the configuration file: /config/db/env
Clearing the configuration file: /config/jobservice/env
Clearing the configuration file: /config/jobservice/config.yml
Generated configuration file: /config/portal/nginx.conf
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/log/rsyslog_docker.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf
Generated configuration file: /config/registry/config.yml
Generated configuration file: /config/registryctl/env
Generated configuration file: /config/registryctl/config.yml
Generated configuration file: /config/db/env
Generated configuration file: /config/jobservice/env
Generated configuration file: /config/jobservice/config.yml
loaded secret from file: /data/secret/keys/secretkey
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir
Note: stopping existing Harbor instance ...
WARN[0000] /opt/harbor/docker-compose.yml: `version` is obsolete
[Step 5]: starting Harbor ...
WARN[0000] /opt/harbor/docker-compose.yml: `version` is obsolete
[+] Running 10/10
✔
Created
Started
Container harbor-db Started
Started
Started
0.2s
✔
Started
1.1s
✔
Started
Started
Started
3.2s
✔
3.4s
3.4s
✔
3.2s
Container harbor-portal
Started
✔
3.0s
✔
4.2s
✔ Container harbor-jobservice Started
5.3s
✔
5.1s
✔ ----Harbor has been installed and started successfully.----
稍等一会,如果所有容器的状态都是 healthy , 说明正常
登录 WEB 界面:http://192.168.108.30

使用
Harbor 里功能比较多,常用的有项目、用户管理、项目定额。
项目:可以针对不同的项目单独创建,每个项目都有自己的镜像地址
用户管理:可以维护用户,不同的项目可以设置不同的维护人员
项目定额:设置项目对应的镜像仓库最大空间容量
下面就按照步骤将一个镜像推送到 Harbor 中。
-
在用户管理中创建名称为 images_admin 的用户:
登录 Harbor Web 界面 → 进入 "用户管理" → 点击 "创建用户" → 填写信息(用户名:images_admin,邮箱:6946630@qq.com,密码:Cloud12#$)→ 确定。

-
在项目中创建名称为 cloud 的项目,并添加 images_admin 为项目管理员:
进入 "项目" → 点击 "新建项目" → 填写项目名称 "cloud",选择公开 → 确定;
进入创建好的 "cloud" 项目 → 点击 "成员" → 点击 "新建成员" → 选择用户 "images_admin",分配角色 "项目管理员" → 确定。


-
配置 Docker 信任 Harbor 仓库:
[root@docker ~]# vim /etc/docker/daemon.json
{
"insecure-registries": ["192.168.108.30"],
"registry-mirrors": [
"https://054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com" ]
}
[root@docker ~]# systemctl restart docker
#重新执行安装命令(如果Harbor未启动)
[root@docker harbor]# ./install.sh
登录 Harbor 仓库:
[root@docker ~]# docker login 192.168.108.30
Username: images_admin
Password:Cloud12#$
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
上传镜像:
# 给镜像打标签,格式:SERVER/PROJECT/IMAGE:TAG
[root@docker ~]# docker tag nginx:latest 192.168.108.30/cloud/nginx:latest
# 上传镜像
[root@docker ~]# docker push 192.168.108.30/cloud/nginx:latest
The push refers to repository [192.168.108.30/cloud/nginx]
11de3d47036d: Pushed
16907864a2d0: Pushed
2bdf51597158: Pushed
0fc6bb94eec5: Pushed
eda13eb24d4c: Pushed
67796e30ff04: Pushed
8e2ab394fabf: Pushed
latest: digest:
sha256:596c783ac62b9a43c60edb876fe807376cd5022a4e25e89b9a9ae06c374299d4 size:
1778
-
下载镜像:
从 Harbor 下载镜像
[root@docker ~]# docker pull 192.168.108.30/cloud/nginx:latest
Using default tag: latest
latest: Pulling from cloud/nginx
a2318d6c47ec: Pull complete
095d327c79ae: Pull complete
bbfaa25db775: Pull complete
7bb6fb0cfb2b: Pull complete
0723edc10c17: Pull complete
24b3fdc4d1e3: Pull complete
3122471704d5: Pull complete
Digest: sha256:596c783ac62b9a43c60edb876fe807376cd5022a4e25e89b9a9ae06c374299d4
Status: Downloaded newer image for 192.168.108.30/cloud/nginx:latest
192.168.108.30/cloud/nginx:latest
卸载 harbor
# 清理容器
[root@docker ~]# cd /opt/harbor/
[root@docker harbor]# docker compose down
# 清理镜像
[root@docker harbor]# docker images |grep harbor|awk '{print $1":"$2}' | xargs
docker rmi
# 清理harbor使用的目录/data
[root@docker harbor]# rm -rf /data
# 删除软件包
[root@docker ~]# rm -f harbor-offline-installer-v2.9.1.tgz
[root@docker ~]# rm -rf /opt/harbor/
至此,Docker 镜像的内容就讨论完了,下节我们对这部分做个小结。
3.13 Docker 镜像小结
本节我们对 Docker 镜像做个小结。
这一部分我们首先讨论了镜像的分层结构,然后学习了如何构建镜像,最后实践使用 Docker Hub 和本 地 registry。
镜像常用操作子命令
- images:显示镜像列表
- history:显示镜像构建历史
- commit:从容器创建新镜像
- build:从 Dockerfile 构建镜像
- tag:给镜像打 tag
- pull:从 registry 下载镜像
- push:将镜像上传到 registry
- rmi:删除 Docker host 中的镜像
- search:搜索 Docker Hub 中的镜像
除了 rmi 和 search, 其他命令都已经用过了。
rmi
rmi 只能删除 host 上的镜像,不会删除 registry 的镜像。
如果一个镜像对应了多个 tag, 只有当最后一个 tag 被删除时,镜像才被真正删除。例如 host 中 busybox 镜像有三个 tag:
[root@docker ~]# docker images busybox
REPOSITORY busybox TAG latest IMAGE ID CREATED SIZE
busybox latest 6fd955f66c23 16 months ago 4.26MB
[root@docker ~]# docker tag busybox:latest busybox:v1
[root@docker ~]# docker tag busybox:latest busybox:v2
[root@docker ~]# docker images busybox
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest
busybox latest 6fd955f66c23 16 months ago 4.26MB
busybox v1 6fd955f66c23 16 months ago 4.26MB
busybox v1
busybox v2 6fd955f66c23 16 months ago 4.26MB
busybox v2
删除其中 busybox:latest 只是删除了 latest tag, 镜像本身没有删除。
[root@docker ~]# docker rmi busybox:latest
Untagged:busybox:latest
[root@docker ~]# docker images busybox
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox v1 6fd955f66c23 16 months ago 4.26MB
busybox v2 6fd955f66c23 16months ago 4.26MB
只有当 busybox:v1 和 busybox:v2 也被删除时,整个镜像才会被删除。
[root@docker ~]#docker rmi busybox:v1
Untagged:busybox:v1
[root@docker ~]# docker rmi busybox:v2
Untagged:busybox:v2
Untagged: busybox@sha256:c230832bd3b0be59a6c47ed64294f9ce71e91b327957920b6929a0caa8353140
Deleted:sha256:6fd955f66c231c1a946653170d096a28ac2b2052a02080c0b84ec082a07f7d12
Deleted:sha256:49b3a50a20394e079b4b4732e1e9e434137c9dd6500a6e1976a00ca88bed4774
search
search 让我们无需打开浏览器,在命令行中就可以搜索 Docker Hub 中的镜像。
[root@docker ~]# docker search httpd
NAME DESCRIPTION STARS OFFICIAL
httpd The Apache HTTP Server Project 4789 [OK]
jitesoft/httpd Apache httpd on Alpine linux. 0
hipache DEPRECATED(upstream);use"traefik","nginx. 85 [OK]
openquantumsafe/httpd Demo of post-quantum cryptography in Apache.. 0
openeuler/httpd 14
betterweb/httpd
vulhub/httpd
paketobuildpacks/httpd
dockette/apache
medolika/httpd
sjlife/httpd
nasoym/httpd
447523269/httpd
yrx2010/httpd
shreedhar26/httpd
7876223/httpd
angeki/thttpd
tonyluo007/httpd
xrowgmbh/httpd
sosradio/httpd
abatool1/httpd
当然,如果想知道镜像都有哪些 tag, 还是得访问 Docker Hub。
保存本地镜像为文件 - save
docker 默认使用 overlay2 存储驱动存储镜像。
[root@docker ~]# docker info | grep 'Storage Driver'
Storage Driver: overlay2
镜像存储在本地 /var/lib/docker/overlay2, 通过文件系统层面拷贝 image, 操作复杂。可以使用 save 命 令,将本地镜像保存为单个文件,并分享给他人使用。
[root@docker ~]# docker save --help
Usage: docker save [OPTIONS] IMAGE [IMAGE...]
Save one or more images to a tar archive (streamed to STDOUT by default)
Aliases:
docker image save, docker save
Options:
-o, --output string Write to a file, instead of STDOUT
示例:
# 将httpd镜像保存为httpd.tar
[root@docker ~]# docker save httpd -o httpd.tar
# 将httpd和hello-world镜像保存为images.tar
[root@docker ~]# docker save httpd hello-world -o images.tar
[root@docker ~]# ls
httpd.tar images.tar
将本地镜像文件导入本地 - load
[root@docker ~]# docker load --help
Usage: docker load [OPTIONS]
Load an image from a tar archive or STDIN
Aliases:
docker image load, docker load
Options:
-i, --input string Read from tar archive file, instead of STDIN
-q, --quiet Suppress the load output
示例:删除本地镜像,并导入本地镜像文件
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd latest
httpd latest 90f191b9781e 11 days ago 148MB
148MB
hello-world latest 74cc54e27dc4 6 months ago 10.1kB
[root@docker ~]# docker rmi httpd:latest hello-world:latest
# 导入镜像
[root@docker ~]# docker load -i images.tar
1bb35e8b4de1: Loading layer 77.88MB/77.88MB
6c5ea8a96778: Loading layer 2.56kB/2.56kB
5f70bf18a086: Loading layer 1.024kB/1.024kB
785236f38f5b: Loading layer 11.41MB/11.41MB
5ca968893b91: Loading layer 62.99MB/62.99MB
b4558c4e936a: Loading layer 3.584kB/3.584kB
Loaded image: httpd:latest
63a41026379f: Loading layer 11.78kB/11.78kB
Loaded image: hello-world:latest
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd latest
httpd latest 90f191b9781e 11 days ago 148MB
148MB
hello-world latest 74cc54e27dc4 6 months ago 10.1kB
说明:如果本地镜像名与导入的镜像重名,则本地的镜像会被覆盖。
清理命令
[root@docker ~]# docker rm -f $(docker ps -aq) # 删除所有容器
[root@docker ~]# docker rmi -f $(docker images -aq) # 删除所有镜像
r rmi busybox:v1
Untagged:busybox:v1
root@docker \~\]# docker rmi busybox:v2 Untagged:busybox:v2 Untagged: busybox@sha256:c230832bd3b0be59a6c47ed64294f9ce71e91b327957920b6929a0caa8353140 Deleted:sha256:6fd955f66c231c1a946653170d096a28ac2b2052a02080c0b84ec082a07f7d12 Deleted:sha256:49b3a50a20394e079b4b4732e1e9e434137c9dd6500a6e1976a00ca88bed4774 ### search search 让我们无需打开浏览器,在命令行中就可以搜索 Docker Hub 中的镜像。 \[root@docker \~\]# docker search httpd NAME DESCRIPTION STARS OFFICIAL httpd The Apache HTTP Server Project 4789 \[OK
jitesoft/httpd Apache httpd on Alpine linux. 0
hipache DEPRECATED(upstream);use"traefik","nginx. 85 [OK]
openquantumsafe/httpd Demo of post-quantum cryptography in Apache... 0
openeuler/httpd 14
betterweb/httpd
vulhub/httpd
paketobuildpacks/httpd
dockette/apache
medolika/httpd
sjlife/httpd
nasoym/httpd
447523269/httpd
yrx2010/httpd
shreedhar26/httpd
7876223/httpd
angeki/thttpd
tonyluo007/httpd
xrowgmbh/httpd
sosradio/httpd
abatool1/httpd
当然,如果想知道镜像都有哪些 tag, 还是得访问 Docker Hub。
### 保存本地镜像为文件 - save
docker 默认使用 overlay2 存储驱动存储镜像。
root@docker \~\]# docker info \| grep 'Storage Driver' Storage Driver: overlay2 镜像存储在本地 /var/lib/docker/overlay2, 通过文件系统层面拷贝 image, 操作复杂。可以使用 save 命 令,将本地镜像保存为单个文件,并分享给他人使用。 \[root@docker \~\]# docker save --help Usage: docker save \[OPTIONS\] IMAGE \[IMAGE...
Save one or more images to a tar archive (streamed to STDOUT by default)
Aliases:
docker image save, docker save
Options:
-o, --output string Write to a file, instead of STDOUT
示例:
将httpd镜像保存为httpd.tar
root@docker \~\]# docker save httpd -o httpd.tar ## 将httpd和hello-world镜像保存为images.tar \[root@docker \~\]# docker save httpd hello-world -o images.tar \[root@docker \~\]# ls httpd.tar images.tar ### 将本地镜像文件导入本地 - load \[root@docker \~\]# docker load --help Usage: docker load \[OPTIONS
Load an image from a tar archive or STDIN
Aliases:
docker image load, docker load
Options:
-i, --input string Read from tar archive file, instead of STDIN
-q, --quiet Suppress the load output
示例:删除本地镜像,并导入本地镜像文件
root@docker \~\]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE httpd latest httpd latest 90f191b9781e 11 days ago 148MB 148MB hello-world latest 74cc54e27dc4 6 months ago 10.1kB \[root@docker \~\]# docker rmi httpd:latest hello-world:latest ## 导入镜像 \[root@docker \~\]# docker load -i images.tar 1bb35e8b4de1: Loading layer 77.88MB/77.88MB 6c5ea8a96778: Loading layer 2.56kB/2.56kB 5f70bf18a086: Loading layer 1.024kB/1.024kB 785236f38f5b: Loading layer 11.41MB/11.41MB 5ca968893b91: Loading layer 62.99MB/62.99MB b4558c4e936a: Loading layer 3.584kB/3.584kB Loaded image: httpd:latest 63a41026379f: Loading layer 11.78kB/11.78kB Loaded image: hello-world:latest \[root@docker \~\]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE httpd latest httpd latest 90f191b9781e 11 days ago 148MB 148MB hello-world latest 74cc54e27dc4 6 months ago 10.1kB 说明:如果本地镜像名与导入的镜像重名,则本地的镜像会被覆盖。 ### 清理命令 \[root@docker \~\]# docker rm -f $(docker ps -aq) # 删除所有容器 \[root@docker \~\]# docker rmi -f $(docker images -aq) # 删除所有镜像 至此,Docker 镜像已经讨论完了。