一、Docker 介绍

1.1 相关资源

资源地址
Docker 官网https://docs.docker.com/
Docker GitHubhttps://github.com/moby/moby
Docker Hub(国内)https://registry.hub.docker.com
Docker Hub(国际,需科学上网)https://hub.docker.com/

1.2 Docker 是什么?

Docker 是一个开源项目,诞生于 2013 年初,最初是 dotCloud 公司内部的一个业余项目。它基于 Google 公司推出的 Go 语言实现。项目后来加入了 Linux 基金会,遵从 Apache 2.0 协议,项目代码在 GitHub 上进行维护。

核心定义: Docker 是一个开源的引擎,可以轻松地为任何应用创建一个轻量级的、可移植的、自给自足的容器。开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何支持 Docker 的机器上运行。

设计思想: Docker 的思想来自于集装箱。集装箱解决了什么问题?在一艘大船上,可以把货物规整地摆放起来,各种各样的货物被装在集装箱里,集装箱之间不会互相影响。这样就不需要专门运送蔬菜的船和专门运送货物的船了。只要这些货物在集装箱里封装得好好的,就可以用一艘大船把他们都运走。Docker 就是类似的理念——云计算就好比大货轮,Docker 就是集装箱。

编排工具类比:

  • PVE / KVM 搭档:PVE 是 KVM 的编排工具
  • OpenStack / KVM 搭档:OpenStack 是 KVM 的编排工具
  • K8s / Docker 搭档:K8s 是 Docker 的编排工具

1.3 Docker 的架构

严格来说,Docker是一个进程。Docker 采用 C/S(客户端/服务器)架构:

  • Docker Client:客户端命令行工具,向服务端发送请求,提供命令行工具
  • Docker Daemon:服务端守护进程,负责管理容器、镜像等,是真正干活的守护进程
  • Registry镜像仓库(如 Docker Hub、Harbor、阿里云容器镜像服务,也可以运行在本地),镜像仓库,用于存储和分发镜像
Docker 与虚拟机的区别
对比项虚拟机Docker 容器
操作系统每个虚拟机运行完整的客户操作系统所有容器共享宿主机内核
启动时间分钟级秒级或毫秒级
资源占用GB 级MB 级
隔离性完全隔离(硬件虚拟化)进程级隔离(Namespace + Cgroups)

虚拟机架构

┌─────────────────────────────────────────────────────────────┐
│                        物理服务器                           │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                宿主机操作系统 (Host OS)               │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Hypervisor (VMware/KVM)                │   │
│  └─────────────────────────────────────────────────────┘   │
│   ┌──────────────┐ ┌──────────────┐ ┌──────────────┐     │
│   │   虚拟机 1    │ │   虚拟机 2    │ │   虚拟机 3    │     │
│   │ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │     │
│   │ │  Guest OS │ │ │ │  Guest OS │ │ │ │  Guest OS │ │     │
│   │ │ (完整内核)│ │ │ │ (完整内核)│ │ │ │ (完整内核)│ │     │
│   │ ├──────────┤ │ │ ├──────────┤ │ │ ├──────────┤ │     │
│   │ │  Bins/   │ │ │ │  Bins/   │ │ │ │  Bins/   │ │     │
│   │ │  Libs    │ │ │ │  Libs    │ │ │ │  Libs    │ │     │
│   │ ├──────────┤ │ │ ├──────────┤ │ │ ├──────────┤ │     │
│   │ │   App    │ │ │ │   App    │ │ │ │   App    │ │     │
│   │ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │     │
│   └──────────────┘ └──────────────┘ └──────────────┘     │
└─────────────────────────────────────────────────────────────┘

每台虚拟机:独立的操作系统 + 独立的内核 → 资源占用大,启动慢,但隔离性好,安全性佳

Docker 容器架构

┌─────────────────────────────────────────────────────────────┐
│                        物理服务器                           │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                宿主机操作系统 (Host OS)               │   │
│  │              (Linux 内核)                           │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                Docker Engine (守护进程)              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│   ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐ │
│   │  容器 1   │  │  容器 2   │  │  容器 3   │  │  容器 4   │ │
│   │ ┌──────┐ │  │ ┌──────┐ │  │ ┌──────┐ │  │ ┌──────┐ │ │
│   │ │Bins/ │ │  │ │Bins/ │ │  │ │Bins/ │ │  │ │Bins/ │ │ │
│   │ │Libs  │ │  │ │Libs  │ │  │ │Libs  │ │  │ │Libs  │ │ │
│   │ ├──────┤ │  │ ├──────┤ │  │ ├──────┤ │  │ ├──────┤ │ │
│   │ │ App  │ │  │ │ App  │ │  │ │ App  │ │  │ │ App  │ │ │
│   │ └──────┘ │  │ └──────┘ │  │ └──────┘ │  │ └──────┘ │ │
│   └──────────┘  └──────────┘  └──────────┘  └──────────┘ │
│                                                             │
│  所有容器共享同一个宿主机内核,只打包应用和依赖             │
└─────────────────────────────────────────────────────────────┘

每个容器:没有独立内核,直接使用宿主机内核 → 轻量级,毫秒级启动。但隔离性差,安全性不及虚拟机
Docker 底层原理(核心知识点)

Namespace 实现资源隔离,Cgroups 实现资源限制。

Cgroup(Control Groups)提供的主要功能如下:

功能说明
资源限制限制任务使用的资源总额,并在超过这个配额时发出提示
优先级分配分配 CPU 时间片数量及磁盘 IO 带宽大小、控制任务运行的优先级
资源统计统计系统资源使用量,如 CPU 使用时长、内存用量等
任务控制对任务执行挂起、恢复等操作
Docker逻辑流程图:

初始的Docker ce软件

容器 ──基于──▶ 镜像 ──从──▶ Docker Hub(官方仓库)
                              │ 拉取镜像
                              │ 但国内访问慢/超时
                              ▼
                        配置镜像加速器
                              │
                              ▼
                   阿里云/ DaoCloud 等国内加速节点
                              │
                              │ 从 Docker Hub 同步缓存
                              ▼
                       docker pull 速度大幅提升

1.4 Docker 的优点

优点说明
运行时的性能快,管理操作(启动、停止、开始、重启等)都以秒或毫秒为单位
敏捷像虚拟机一样敏捷,而且会更便宜,在裸机上部署像点按钮一样简单
灵活将应用和系统“容器化”,不添加额外的操作系统
轻量在一台服务器上可以部署 100~1000 个容器
便宜开源的,免费的,低成本的

1.5 Docker 的缺点

所有容器共用 Linux 内核资源,资源能实现最大限度利用,但资源隔离性差,所以在安全上也可能会存在漏洞。

版本说明

docker主要分为企业版(Docker EE)和社区版(Docker CE )

版本全称费用特点适用场景
Docker CECommunity Edition(社区版)免费开源,包含 Docker 核心功能,由社区维护个人开发者、小型团队、学习实验
Docker EEEnterprise Edition(企业版)商业付费CE 基础上增加安全扫描、镜像签名、企业级支持等中大型企业的核心生产环境

请添加图片描述

  Docker
    ├── Docker CE(社区版,免费)
    │     ├── nightly(日更版)← 更新最频繁,内容最新,最不稳定
    │     ├── test(测试版)← 预发布版本,包含新功能
    │     ├── Stable(稳定版)← 生产环境推荐,稳定性最佳
    │     └── Edge(边缘版)← 仅供测试/尝鲜。目前已经淡出,被test取代
    └── Docker EE(企业版,付费)

二、安装 Docker

注意!本次项目全程使用欧拉系统进行操作,版本为openEuler-22.03,可前往镜像网站进行下载:https://mirror.accum.se/mirror/openeuler.org/openEuler-22.03-LTS-SP2/ISO/x86_64/
欧拉系统可在VMware 正常安装,过程不再赘述。
此外,本次实验全程使用MobaXterm/WindTerm进行远程连接

2.1 实验环境

项目配置
主机 IP192.168.1.11
内存4GiB
CPU2vCPU

2.2 配置时间同步

# 安装时间同步工具
[root@hd1 ~]# yum install -y ntp ntpdate
[root@hd1 ~]# ntpdate cn.pool.ntp.org

# 编写计划任务,每小时同步一次
[root@hd1 ~]# crontab -e
* */1 * * * /usr/sbin/ntpdate cn.pool.ntp.org

# 重启 crond 服务使配置生效
[root@hd1 ~]# systemctl restart crond

2.3 安装 Docker-CE

配置 Docker-CE 国内 YUM 源(阿里云)

这里我们需要指定 Docker-CE 这个软件的安装地址

# 下载 Docker-CE 仓库文件配置文件docker-ce.repo,里面写的是 Docker 仓库的地址和配置信息
[root@hd1 ~]# wget http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@hd1 ~]# mv docker-ce.repo /etc/yum.repos.d/

# 修改 docker-ce.repo 的内容如下(只保留第一个[docker-ce-stable]仓库项,并修改baseurl为稳定版本的仓库地址)
[root@hd1 yum.repos.d]# cat docker-ce.repo
[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8.0/x86_64/stable/  #镜像仓库地址
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/docker-ce/linux/centos/gpg

# 安装 Docker 依赖包
[root@hd1 ~]# yum install -y device-mapper-persistent-data lvm2

# 安装 Docker-CE
[root@hd1 ~]# yum install docker-ce -y

# 启动 Docker 服务并设置开机自启
[root@hd1 ~]# systemctl start docker && systemctl enable docker
[root@hd1 ~]# systemctl status docker

# 查看 Docker 版本信息
[root@hd1 ~]# docker version

wget拉取了软件仓库的repo配置文件(或者说是仓库文件的模板,这个软件仓库用来下载docker ce软件),我们根据实际情况修改这个配置文件。
其实可以不拉取也可以,我们自己创建repo文件写入对应配置,也可以达到同样的效果。

2.4 开启包转发功能

开启包转发(IP Forward)是让 Linux 内核具备“三层路由转发”能力,即允许内核将一个网络接口(如 docker0 网桥)收到的数据包,根据路由表转发到另一个网络接口(如宿主机的 eth0 物理网卡)。

Docker 的默认网络模式(bridge)下,宿主机充当了容器的路由器/网关。如果不开启转发,这个“路由器”功能就被禁用了。

开启包转发后,宿主机实际上变成了一个路由器。为了防止容器被当作跳板攻击外部网络,Docker 会同时在 iptables 的 FORWARD 链中插入严格的过滤规则(默认 DROP 所有非 Docker 网络的转发流量),所以开启转发本身不会引入严重安全风险,但运维时需注意不要轻易 iptables -F 清空规则,否则容器会变成“肉鸡”

# 开启 IP 转发
[root@hd1 ~]# cat /etc/sysctl.conf
......
kernel.sysrq=0
net.ipv4.ip_forward=1   #修改本行为1,其余不变即可
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.default.send_redirects=0
net.ipv4.conf.all.accept_source_route=0
net.ipv4.conf.default.accept_source_route=0
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv4.conf.all.secure_redirects=0
net.ipv4.conf.default.secure_redirects=0
net.ipv4.icmp_echo_ignore_broadcasts=1
net.ipv4.icmp_ignore_bogus_error_responses=1
net.ipv4.conf.all.rp_filter=1
net.ipv4.conf.default.rp_filter=1
net.ipv4.tcp_syncookies=1

#重新加载内核参数配置文件,让其中的设置立即生效
[root@hd1 ~]# sysctl -p 

# 重启 Docker 服务
[root@hd1 ~]# systemctl restart docker

2.5 配置 Docker 镜像加速器

登录阿里云镜像仓库:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors

如果没有开通,可先开通阿里云的镜像服务。

请添加图片描述
将上图框中内容粘贴到/etc/docker/daemon.json文件中(加速地址每个人都不一样,尽量使用自己的)

# /etc/docker/daemon.json 默认是不存在的,需要自己手动创建。这个文件是官方规定的默认配置文件路径和文件名。在其中加入自己的加速地址
[root@hd1 ~]# vim /etc/docker/daemon.json
#这是我找到的能用的几个加速地址
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://docker.1ms.run",
    "https://docker.xuanyuan.me"
  ]
}

# 让配置生效
[root@hd1 ~]# systemctl daemon-reload  #重新读取配置文件
[root@hd1 ~]# systemctl restart docker #重启服务

三、Docker 的基本用法

容器的特点
  • 镜像:只读的文件
  • 容器:根据镜像生成的“应用”(确切说,是 一个轻量级系统+应用程序 ),同一个镜像可以开多个容器

容器是为应用而生的,倘若应用挂掉,容器也会‘自杀’。容器的作用就是让某个应用能以标准化的方式运行。 它的生命周期和主进程是绑定的,这是它和虚拟机最本质的区别之一

容器的本质:
容器是为运行某个特定功能进程而存在的临时环境。它从启动的那一刻起,就围绕着主进程的生命周期展开:主进程启动,容器就运行;主进程结束,容器停止

安装容器之后,宿主机会多一个网卡(与虚拟机有些类似)docker0,术语逻辑网卡,是容器和宿主机通信的桥梁

3.1 镜像管理

docker load -i:解压离线镜像包(-i:指定镜像包)
docker images:查看本地镜像
docker save -o [压缩包名 包名:标签]:将镜像导出为离线压缩包

# 先拉取镜像。此步骤需联网,如果无法拉取,应该是/etc/docker/daemon.json文件中加速地址的问题,此时建议问AI获取当前可用的公共镜像资源)
[root@hd1 ~]# docker pull nginx:latest
latest: Pulling from library/nginx
062e450697fa: Pull complete 
82454cdbf456: Pull complete 
3c7ab7949321: Pull complete 
cacfcdd01f30: Pull complete 
b6698f04e005: Pull complete 
2bedaf25031a: Pull complete 
d26f27cc8c41: Pull complete 
Digest: sha256:5a88c9c45479443d7be2eadc894b4ed0a9801bae03d97a5760ae13b5c2005942
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

#查看本地镜像验证拉取是否成功 
[root@hd1 ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
nginx        latest    4e5db4761e0f   2 days ago   161MB


# 如果有本地的离线镜像资源包,可以使用load命令加载离线镜像包:
[root@hd1 ~]# docker load -i nginx.tar.gz


# 从docker官方远程仓库下载镜像
[root@hd1 ~]# docker pull redis

# 从个人仓库拉取镜像  仓库路径+要拉取的镜像名(如果有非latest的标签,一定要加上标签名。latest标签的标签名可以省略)
[root@hd1 ~]# docker pull registry.cn-hangzhou.aliyuncs.com/wangmeng100/centos:v1
[root@hd1 ~]# docker pull registry.cn-hangzhou.aliyuncs.com/wangmeng100/nginx:latest

# 查看本地镜像
[root@hd1 ~]# docker images

# 将镜像导出为离线压缩包
[root@hd1 ~]# docker save -o alpine2026.tar.gz  alpine:latest

# 删除镜像(如果 tag 是 latest,可以省略)
# 方法1
[root@hd1 ~]# docker rmi -f alpine:latest
[root@hd1 ~]# docker rmi -f alpine:latest 
Untagged: alpine:latest
Untagged: alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b
Deleted: sha256:d529dd0c6e5597ac7e4a3e2dea65c3fcc6173f4cae713c409265c1dd9914a11b
Deleted: sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c
#再使用images查看,已经被删除
[root@hd1 ~]# docker images
REPOSITORY                                             TAG       IMAGE ID       CREATED       SIZE
nginx                                                  latest    4e5db4761e0f   2 days ago    161MB
redis                                                  latest    da3958f512ee   4 days ago    143MB
registry.cn-hangzhou.aliyuncs.com/wangmeng100/centos   v1        eeb6ee3f44bd   4 years ago   204MB


# 方法2(如果 tag 是 latest,可以省略标签)
[root@hd1 ~]# docker rmi -f alpine

请添加图片描述
TAG是标签,我们在官网拉下的镜像标签默认是latest。
无论是个人仓库还是官方仓库,标签默认是latest,同样的latest标签结尾的在拉取时可以省略标签名

3.2 容器的基本操作

交互式启动容器(较少用)

输入 exit 退出容器后,容器也会停止,不会再在前台运行。

# 以交互式方式启动容器并进入容器环境
[root@hd1 ~]# docker run --name=hello -it nginx:latest /bin/bash
root@448549093fae:/# 

参数说明:

  • --name:指定当前容器的名字(仅用来标识)
  • -i:交互式
  • -t:分配终端
  • nginx:latest:指定启动 Docker 需要的镜像
  • /bin/bash:指定你的 Shell(解释器) 类型为 Bash,也就是终端的解释器

注意:交互式进入容器之后,使用exit退出容器的同时,容器会关闭

守护进程方式启动容器(较常用)

docker ps :查看正在运行的容器(-a参数可以查看所有容器,包括已退出的容器)

守护进程方式启动不需要指定解释器,exec登陆进去再指定

# 以守护进程方式启动容器
[root@hd1 ~]# docker run --name=hello1 -td nginx:latest
#这里就可以看到我们的容器了
[root@hd1 ~]# docker ps
CONTAINER ID   IMAGE          COMMAND                   CREATED         STATUS         PORTS     NAMES
89a61bc7b6f7   nginx:latest   "/docker-entrypoint.…"   7 seconds ago   Up 6 seconds   80/tcp    hello1

-d:在后台运行容器(detach 模式)

使用守护进行方式启动的容器,后续登录进去,再exit退出容器,不会让容器关闭。

如果运行的时候就是-it交互式启动容器,再exit,容器会关闭;但是如果一开始就是-td守护进程方式启动容器,再exec登录进容器,再退出,不会关闭容器,容器会回到后台状态

后台运行容器是否需要加 -t,取决于容器的主进程是否依赖伪终端(TTY)。

  • 主进程是交互式 Shell(如 /bin/sh/bin/bash)时必须加 -t,否则 Shell 会因无终端而退出,容器立即停止。(也就是说容器运行交互式进程时必须使用-t)
  • 主进程是服务类进程(如 nginxredis)时不需要 -t,直接 -d 即可正常运行,-t 在此场景下无效且多余。

一个典型的特例:Alpine 镜像的默认主进程就是 /bin/sh,所以在后台运行时 docker run -td alpine 才是正确的写法(同时需要 -d-t)。

登录到容器中

注意!docker exec 只能在容器处于 “运行中”(Running) 状态时才能使用,需要与之前的run进行区分。run 是“新建并启动”,exec 是“进入已运行中的”

[root@hd1 ~]# docker exec -it hello1 /bin/bash
root@89a61bc7b6f7:/# 
查看容器
# 查看正在运行的容器
[root@hd1 ~]# docker ps
CONTAINER ID   IMAGE          COMMAND                   CREATED         STATUS         PORTS     NAMES
89a61bc7b6f7   nginx:latest   "/docker-entrypoint.…"   7 seconds ago   Up 6 seconds   80/tcp    hello1

# 查看所有容器(包括运行和退出的)
[root@hd1 ~]# docker ps -a
CONTAINER ID   IMAGE          COMMAND                   CREATED              STATUS                          PORTS     NAMES
89a61bc7b6f7   nginx:latest   "/docker-entrypoint.…"   About a minute ago   Up About a minute               80/tcp    hello1
448549093fae   nginx:latest   "/docker-entrypoint.…"   2 minutes ago        Exited (0) About a minute ago             hello
容器的启停

docker stop 容器名/CONTAINER ID :停止容器
docker start 容器名 :启动容器

注意!docker start 默认是“后台运行”(detach),也就是说会进入守护进程的方式,此时可以通过exec登录容器

也就是说,如果容器1以交互式开启,然后退出容器(此时容器会关闭),使用docker start启动容器之后,容器会变成后台运行,也就是我们之前的守护进程方式启动容器的状态
请添加图片描述

# 停止容器
[root@hd1 ~]# docker stop hello1
#使用容器名或者容器id都可以
[root@hd1 ~]# docker stop 89a61bc7b6f7

# 启动容器
[root@hd1 ~]# docker start hello1
#同样的,使用容器id也可以
[root@hd1 ~]# docker start 89a61bc7b6f7 

3.3 端口映射

docker run:新开并启动一个新容器
-P:将容器内部的端口号映射到物理机的随机端口
-p 宿主机端口号:容器端口号:将容器内部的端口号映射到物理机的指定端口
-d:后台运行

# -P:将容器内部的端口号映射到物理机的随机端口
[root@hd1 ~]# docker run --name=nginx3 -P -d nginx
#本命令会新开一个容器

# 查看端口映射
[root@hd1 ~]# docker ps
CONTAINER ID   IMAGE          COMMAND                   CREATED          STATUS          PORTS                                     NAMES
9969930fd1b9   nginx          "/docker-entrypoint.…"   32 seconds ago   Up 30 seconds   0.0.0.0:32768->80/tcp, :::32768->80/tcp   nginx3
89a61bc7b6f7   nginx:latest   "/docker-entrypoint.…"   20 minutes ago   Up 13 minutes   80/tcp                                    hello1
#可以看到,容器的80端口映射到了宿主机的32768端口

# -p:指定映射到固定的端口
[root@hd1 ~]# docker run --name=nginx5 -p 9000:80 -d nginx:latest
[root@hd1 ~]# docker ps
CONTAINER ID   IMAGE          COMMAND                   CREATED          STATUS          PORTS                                     NAMES
9f7fc50bae33   nginx:latest   "/docker-entrypoint.…"   3 seconds ago    Up 2 seconds    0.0.0.0:9000->80/tcp, :::9000->80/tcp     nginx5
9969930fd1b9   nginx          "/docker-entrypoint.…"   2 minutes ago    Up 2 minutes    0.0.0.0:32768->80/tcp, :::32768->80/tcp   nginx3
89a61bc7b6f7   nginx:latest   "/docker-entrypoint.…"   22 minutes ago   Up 14 minutes   80/tcp                                    hello1
#可以看到,我们nginx5容器的80端口映射到了宿主机的9000端口

补充:
此时可以在浏览器访问虚拟机映射80端口的端口,如192.168.1.11:9000
但我们的宿主机并没有安装nginx。这就是容器的作用,容器就是一个功能盒子。

#登录进容器
[root@hd1 ~]# docker exec  -it nginx5 /bin/bash
#修改网页根目录下的index.html
root@9f7fc50bae33:/# echo 1111 >  /usr/share/nginx/html/index.html 
root@9f7fc50bae33:/# curl 192.168.1.11:9000
1111

此时在windows浏览器访问,会展示出来修改后的页面

3.4 Docker Commit:将容器提交为镜像

docker commit 容器名/容器id 镜像名:标签:将容器提交为镜像

# 1. 创建一个容器
[root@hd1 ~]# docker run --name=hello200 -d nginx

# 2. 进入容器内部,对容器做出改变
[root@hd1 ~]# docker exec -it hello200 /bin/bash
root@da59b5cb6d3e:/# cd /usr/share/nginx/html/
root@da59b5cb6d3e:/usr/share/nginx/html# echo 1111111111111111111111111 > index.html

# 3. 将容器打包成镜像
[root@hd1 ~]# docker commit hello200 nginx:v2

# 4. 查看生成的镜像
[root@hd1 ~]# docker images
REPOSITORY                TAG       IMAGE ID       CREATED         SIZE
nginx                     v2        9f5923b34976   8 seconds ago   231MB

# 5. 用 nginx:v2 运行容器,验证修改
[root@hd1 ~]# docker run --name=hello202 -p 8888:80 -d nginx:v2
[root@hd2 ~]# docker exec -it hello202 /bin/bash
root@69296683ebc8:/# curl 127.0.0.1
1111111111111111111111111

# 6. 将镜像打包,通过 U 盘等设备扩散
[root@hd1 ~]# docker save -o abc.tar.gz nginx:v2

3.5 Docker CP:在宿主机与容器之间拷贝文件

docker cp 宿主机地址 容器名:容器中地址 :将宿主机内容拷贝到容器
docker cp 容器名:容器中地址 宿主机地址 :将容器内容拷贝的宿主机

# 将宿主机上的 11.txt 拷贝到 nginx5 容器的 / 目录
[root@hd1 ~]# docker cp 11.txt nginx5:/
Successfully copied 2.05kB to nginx5:/

# 将容器内部文件拷贝到宿主机上
[root@hd1 ~]# docker cp nginx5:/etc/nginx/nginx.conf .
Successfully copied 2.56kB to /root/.

3.6 其他常用命令

命令说明
docker history 镜像名[:标签]显示镜像的构建历史
docker inspect hello202获取 Docker 对象(容器,镜像,网络,卷等)的底层信息 (已经关闭的容器也能看到)
docker logs hello202获取容器日志非常重要
docker ps [-a]显示正在运行容器(-a显示所有容器,包括已经退出的)
docker rm [-f] hello202删除容器
docker top hello202显示正在运行容器的进程
docker stats查看容器资源占用情况(本命令有几秒钟延迟)
docker stats hello202查看某个容器的资源占用
#显示镜像的构建历史,输出内容需要从下往上看,最顶部是最新构建的内容
[root@hd1 ~]# docker history nginx:V5
#看可以看得到,最顶部是最新内容,显示更改时间为5分钟前
IMAGE          CREATED         CREATED BY                                       SIZE      COMMENT
23abe801deb2   5 minutes ago   nginx -g daemon off;                             1.34kB    
4e5db4761e0f   2 days ago      CMD ["nginx" "-g" "daemon off;"]                 0B        buildkit.dockerfile.v0
<missing>      2 days ago      STOPSIGNAL SIGQUIT                               0B        buildkit.dockerfile.v0
<missing>      2 days ago      EXPOSE map[80/tcp:{}]                            0B        buildkit.dockerfile.v0
<missing>      2 days ago      ENTRYPOINT ["/docker-entrypoint.sh"]             0B        buildkit.dockerfile.v0
<missing>      2 days ago      COPY 30-tune-worker-processes.sh /docker-ent…   4.62kB    buildkit.dockerfile.v0
<missing>      2 days ago      COPY 20-envsubst-on-templates.sh /docker-ent…   3.03kB    buildkit.dockerfile.v0
<missing>      2 days ago      COPY 15-local-resolvers.envsh /docker-entryp…   389B      buildkit.dockerfile.v0
<missing>      2 days ago      COPY 10-listen-on-ipv6-by-default.sh /docker…   2.12kB    buildkit.dockerfile.v0
<missing>      2 days ago      COPY docker-entrypoint.sh / # buildkit           1.62kB    buildkit.dockerfile.v0
<missing>      2 days ago      RUN /bin/sh -c set -x     && groupadd --syst…   82.7MB    buildkit.dockerfile.v0
<missing>      2 days ago      ENV DYNPKG_RELEASE=1~trixie                      0B        buildkit.dockerfile.v0
<missing>      2 days ago      ENV PKG_RELEASE=1~trixie                         0B        buildkit.dockerfile.v0
<missing>      2 days ago      ENV ACME_VERSION=0.4.1                           0B        buildkit.dockerfile.v0
<missing>      2 days ago      ENV NJS_RELEASE=1~trixie                         0B        buildkit.dockerfile.v0
<missing>      2 days ago      ENV NJS_VERSION=1.0.0                            0B        buildkit.dockerfile.v0
<missing>      2 days ago      ENV NGINX_VERSION=1.31.3                         0B        buildkit.dockerfile.v0
<missing>      2 days ago      LABEL maintainer=NGINX Docker Maintainers <d…   0B        buildkit.dockerfile.v0
<missing>      5 days ago      # debian.sh --arch 'amd64' out/ 'trixie' '@1…   78.6MB    debuerreotype 0.17


#获取docker对象(容器,镜像,网络,卷等)所有底层详细信息。inspect,检查,探测的意思
[root@hd1 ~]# docker inspect hello202
#输出信息太多,通常我们会用 --format 参数或 grep 来过滤

#获取容器日志(非常重要)
[root@hd1 ~]#docker logs hello202

#列出正在运行的容器
[root@hd1 ~]#docker ps 

#列出所有容器,包括退出的
[root@hd1 ~]#docker ps -a

#移除一个或者多个容器(加-f强制删除)
[root@hd1 ~]#docker rm hello202

#显示正在运行容器的进程
[root@hd1 ~]#docker top  hello202
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                19155               19133               0                   11:03               ?                   00:00:00            nginx: master process nginx -g daemon off;
101                 19196               19155               0                   11:03               ?                   00:00:00            nginx: worker process
101                 19197               19155               0                   11:03               ?                   00:00:00            nginx: worker process
101                 19198               19155               0                   11:03               ?                   00:00:00            nginx: worker process
101                 19199               19155               0                   11:03               ?                   00:00:00            nginx: worker process



#查看所有的容器资源占用情况(动态命令)
[root@hd1 ~]#docker stats
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O     PIDS
9f7fc50bae33   nginx5    0.00%     3.891MiB / 3.297GiB   0.12%     8.82kB / 7.94kB   0B / 24.6kB   5
9969930fd1b9   nginx3    0.00%     3.812MiB / 3.297GiB   0.11%     1.19kB / 0B       0B / 16.4kB   5
89a61bc7b6f7   hello1    0.00%     3.781MiB / 3.297GiB   0.11%     1.41kB / 0B       0B / 0B       5
#查看某个容器的资源占用情况
[root@hd1 ~]#docker stats hello202
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O     PIDS
9f7fc50bae33   nginx5    0.00%     3.891MiB / 3.297GiB   0.12%     8.82kB / 7.94kB   0B / 24.6kB   5
验证容器隔离性

在宿主机上可以看到容器内部的进程,说明容器隔离性相对较弱

[root@hd1 ~]# ps -ef | grep nginx
root       18814   18793  0 11:01 ?        00:00:00 nginx: master process nginx -g daemon off;
101        18856   18814  0 11:01 ?        00:00:00 nginx: worker process
101        18857   18814  0 11:01 ?        00:00:00 nginx: worker process
101        18858   18814  0 11:01 ?        00:00:00 nginx: worker process
101        18859   18814  0 11:01 ?        00:00:00 nginx: worker process
root       19155   19133  0 11:03 ?        00:00:00 nginx: master process nginx -g daemon off;
101        19196   19155  0 11:03 ?        00:00:00 nginx: worker process
101        19197   19155  0 11:03 ?        00:00:00 nginx: worker process
101        19198   19155  0 11:03 ?        00:00:00 nginx: worker process
101        19199   19155  0 11:03 ?        00:00:00 nginx: worker process
root       21888   15210  0 11:43 pts/2    00:00:00 grep --color=auto nginx
容器的杀死与停止
#正常停止容器与杀死容器的退出代码是不同的。杀死一个或者多个容器,会导致异常退出,退出代码非0
#杀死一个容器
[root@hd1 ~]# docker kill nginx3
nginx3
#查看退出代码
[root@hd1 ~]# docker inspect nginx3
......
        "State": {
            "Status": "exited",
            "Running": false,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 0,
            "ExitCode": 137,   #退出代码非0。经典代码137,代表强制退出
            "Error": "",
            "StartedAt": "2026-07-18T03:01:10.08815254Z",
            "FinishedAt": "2026-07-18T03:44:33.43035631Z"
        },
......
#启动一下刚刚杀死的容器nginx3
[root@hd1 ~]# docker start nginx3
#正常停止容器
[root@hd1 ~]# docker stop hello202
#查看退出代码
[root@hd1 ~]# docker inspect hello202
......
        "State": {
            "Status": "exited",
            "Running": false,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 0,
            "ExitCode": 0,  #退出代码为0,代表正常退出
            "Error": "",
            "StartedAt": "2026-07-18T03:47:02.417518643Z",
            "FinishedAt": "2026-07-18T03:47:55.373019687Z"
        },
......
容器与镜像的删除

docker rm [-f] 容器名/容器id :删除容器
docker rmi [-f] 镜像名/镜像id:删除镜像

如果镜像正在被容器使用,直接删除会报错:
Error response from daemon: conflict: unable to remove repository reference “nginx” (must force) - container xxx is using its referenced image xxx

此时要么先删除依赖镜像的容器,要么加-f强行删除

生产环境中不建议强制删除

# 删除一个或多个容器
[root@hd1 ~]# docker rm hello202

# 强制删除正在运行的容器
[root@hd1 ~]# docker rm -f hello202

# 批量删除所有容器
[root@hd1 ~]# docker rm -f $(docker ps -aq)

# 批量删除所有镜像
[root@hd1 ~]# docker rmi -f $(docker images -q)

$() : 将括号中的语句/命令的输出结果作为值
-q:静默输出
-a:表示所有容器(包括已停止的)

docker images -q:输出所有镜像id

[root@hd1 ~]# docker images -q
23abe801deb2
4e5db4761e0f
da3958f512ee
eeb6ee3f44bd

docker ps -aq:输出所有容器id

[root@hd1 ~]# docker ps -aq
9f7fc50bae33
9969930fd1b9
448549093fae

3.7 容器的属性限制

Docker Update可以限制运行中的容器的属性。
语法:docker update 选项=值 容器名/容器id

Docker Update --restart重启策略

策略说明
no默认策略,容器异常退出时不重启
on-failure容器非正常退出时(退出状态非0)重启
on-failure:3非正常退出时重启,最多重启3次
always容器非正常退出时总是重启
# 设置容器开机自启(通过 update 指定重启策略为 always)
[root@hd1 ~]# docker update --restart=always 2ef06a364009

# 限制容器内存(-m 设置内存限制,--memory-swap 设置内存+交换分区限制)
[root@hd1 ~]# docker update -m 64M --memory-swap 64M nginx2
#可以通过docker stats 查看

分配cpu:
0-3 指代第0个到第四个
0,1 :把0和1分配给

此外,docker run 也可以进行容器属性的限制

# 绑定 CPU 核(编号为 5 的 CPU 核)
[root@hd1 ~]# docker run --cpuset-cpus 5 -d nginx:latest

# 限制容器使用 0.1 (10%)的 CPU 算力
[root@hd1 ~]# docker run --name abc --cpus 0.1 -d nginx

# 启动容器时限制内存(run的时候不指定容器名,会自动生成一个容器名)
[root@hd1 ~]# docker run -m 128M -d nginx

–cpuset-cpus:指定的是 物理/逻辑 CPU 核心编号,只能是整数。用于让容器独占特定核心
–cpus : CPU 使用时间份额,表示占用总 CPU 算力的百分比,可以是小数。用于限制容器整体 CPU 使用率

毫核(millicore)是 CPU 资源的逻辑切片单位
1cpu 1核=1000mcpu (毫核)

grep命令解析:grep -i -C 2 cpu
-i :不区分大小写
-C 2:表示查看匹配行的上下两行
补充:
-B N 显示匹配行之前的 N 行(Before)
-A N 显示匹配行之后的 N 行(After)
-C N 显示匹配行前后各 N 行(Context)

3.8 使用容器部署 MySQL

[root@hd1 ~]# docker run --name mysql80 \
 -e MYSQL_ROOT_PASSWORD=123456 \  # -e:设置容器的环境变量,这里通过环境变量来设置数据库的密码
 -p 33060:3306 \
 --privileged \   # --privileged:特权容器,容器内使用真正的 root 用户
 -d mysql:8.0



# 通过访问宿主机的 33060 端口来访问数据库
[root@hd1 ~]# dnf -y install mysql  #提供mysql命令 
[root@hd1 ~]# mysql -uroot -p123456 -P 33060 -h 192.168.1.11

加了 --privileged,可以让容器内的 root 用户获得宿主机 root 的几乎所有权限(实战不建议)
不加 --privileged,容器内的 root 用户在宿主机看来只是一个受限的普通用户

3.9 选择镜像的建议

生产环境,尽可能选体积较小的镜像

下面是两个常用的操作系统镜像:

镜像说明
BusyBox集成了一百多个最常用 Linux 命令和工具的软件,是 Linux 工具里的“瑞士军刀”
Alpine面向安全的轻型 Linux 发行版,经典最小镜像,基于 BusyBox,功能比 BusyBox 完善(相当于一个小型的操作系统。不支持yum命令,但支持apt命令)

最佳实践: 无论是制作镜像还是下载镜像,优先选择 Alpine 类型的镜像。

#需要指定一个 -t 才能在后台运行
[root@hd1 ~]# docker run -t -d alpine:latest

#登录alpine容器不能使用/bin/bash,因为Alpine 为了追求极致小巧,默认不包含 Bash,只带了更轻量的 sh
[root@hd1 ~]# docker exec -it 4a /bin/sh
#注意这里4a是容器id简写,因为之前运行没有指定容器名使用用容器id指定

四、Docker 数据卷管理

4.1 数据卷的概念

创建新容器时,可以在基础层之上添加一个新的可写层,该层通常称为“容器层”。对运行中的容器所做的所有更改(例如写入新文件、修改现有文件和删除文件)都将写入这个薄可写容器层。

什么是数据卷?

数据卷类似于 U 盘,和容器保持松耦合。数据卷是经过特殊设计的目录,可以绕过联合文件系统(UFS),为一个或者多个容器提供访问。数据卷设计的目的在于数据的永久存储,它完全独立于容器的生存周期。因此,Docker 不会在容器删除时删除其挂载的数据卷,也不会存在类似的垃圾收集机制对容器引用的数据卷进行处理。同一个数据卷可以支持多个容器的访问。

两种挂载方式:

bind Bind Mount 宿主机绝对路径挂载

数据流向:

   宿主机目录 (/my/html)  ──bind mount──▶  容器 (/usr/share/nginx/html)
         │                                        │
         │  修改文件                               │  实时同步
         ▼                                        ▼
    宿主机文件变化  ◀─── 双向同步 ───▶  容器内文件变化

Volume 挂载Docker 卷
数据流向:

  宿主机 Docker 区域 (/var/lib/docker/volumes/卷名)  ──volume mount──▶  容器内挂载点
                              │
                              ▼
                      Docker 全权管理
                      不依赖宿主机路径(宿主机删除对应目录也不会影响容器数据)
                      生产环境推荐使用

请添加图片描述
container:容器
Memory:容器的临时存储(容器重启即消失,对应 tmpfs 挂载)
Filesystem:宿主机真正的硬盘存储(持久化数据)
Docker area:Docker 管理的专属存储区域
Memory:容器的临时存储(容器重启即消失,对应 tmpfs 挂载)
(/var/lib/docker/volumes/)

4.2 Bind Mount(宿主机绝对路径挂载)

# 将宿主机上的 /my/html 和容器中的 /usr/share/nginx/html 进行映射
# 所有以 "/" 开始的路径都认为是 bind mount
[root@hd1 html]# mkdir /my/html -p
[root@hd1 html]# docker run --name=nginxa -d -p 8000:80 -v /my/html:/usr/share/nginx/html nginx:latest 

#注意,我们刚挂载一个空的目录/my/html到容器,所以容器内的 /usr/share/nginx/html 也是空的,此时访问会显示403

#修改index.html文件
[root@hd1 html]# echo 111111 >  /my/html/index.html 
#访问验证
[root@hd1 html]# curl http://192.168.1.11:8000/
111111

-v 宿主机路径:容器路径数据卷挂载(Volume)。将宿主机的目录挂载到容器的目录(Nginx 的网页根目录)。
这样修改宿主机上的网页文件,会实时同步到容器内

Bind Mount 挂载,宿主机路径会直接占据容器路径,如果宿主机目录为空,会清空容器内的目标目录

4.3 Volume 挂载(生产环境推荐)

Volume 挂载分为具名卷(有名称的卷)和匿名卷(没有名称的卷)。无论是具名卷还是匿名卷,默认都在宿主机的 /var/lib/docker/volumes/ 目录之下。

若卷为空,则会复制容器中内容到卷中。若卷中本来就有内容,则不会复制容器内容,避免覆盖

# 具名卷:创建名为 nginx 的卷,并保存容器路径 /usr/share/nginx/html 中的内容
[root@hd1 html]# docker run --name nginx12 -d -P -v nginx:/usr/share/nginx/html nginx:latest

#此时在volume目录下可以看到我们的具名卷
[root@hd1 html]# ls /var/lib/docker/volumes/
backingFsBlockDev  metadata.db  nginx
#数据就存储在卷目录下的 _data 子目录中
[root@hd1 html]# ls /var/lib/docker/volumes/nginx/
_data 

#添加index.html文件
[root@hd1 html]# echo 666666 >  /var/lib/docker/volumes/nginx/_data/index.html 
#访问验证
[root@hd1 html]# curl http://192.168.1.11:8000/
666666


# 匿名卷:不指定卷名,由 Docker 自动生成
[root@hd1 html]# docker run --name nginx1 -d -P -v /usr/share/nginx/html nginx:latest

# 列出所有卷
[root@hd1 ~]# docker volume ls
402430d5f06b596a4c400136f3bf285e4332877e2643085db1551e784fd63155  metadata.db
backingFsBlockDev                                                 nginx

# 查看匿名容器的挂载详情(匿名容器没有名称,但有一个哈希id进行标识)
[root@hd1 ~]# docker inspect nginx1
#注意,只有匿名卷才有这串哈希值卷名
"Mounts": [
    {
        "Type": "volume",
        "Source": ls"/var/lib/docker/volumes/402430d5f06b596a4c400136f3bf285e4332877e2643085db1551e784fd63155/_data",
    }
]

# 移除没有被使用的卷。prune命令只会删除未被任何容器使用的匿名卷
[root@hd1 ~]# docker volume prune

#删除匿名卷
[root@hd1 ~]# docker volume rm 402430d5f06b596a4c400136f3bf285e4332877e2643085db1551e784fd63155

#删除具名卷
[root@hd1 ~]# docker volume rm nginx

#注意,如果还有容器在使用这个卷,卷是删不掉的
#查看哪些容器在用这个卷
[root@hd1 html]# docker ps -a --filter volume=nginx
# 删除相关容器(假设容器名叫 nginx12)
[root@hd1 html]# docker rm -f nginx12
#再删卷就行了
[root@hd1 html]# docker volume rm nginx

进行两种挂载之后,即使容器被删除,宿主机对应目录也会保留相应的数据。两种挂载方式,如果在容器中修改内容,宿主机也会同步,宿主机修改内容,容器也会同步。并且在容器内删除文件,宿主机上对应的文件也会被删除,双向同步。

4.4 --mount 参数

我们可以发现,无论是 Volume 还是 Bind Mount,都用 -v–mount 参数,他们的语法基本是一致的。因为 Docker 是通过 “第一个字段的格式” 来区分它们的,带 / 是bind挂载,不带 / 是volume卷

虽然 -v 依然能用,但 Docker 官方现在更推荐使用 --mount 参数,因为它语法更明确,不会混淆
语法: --mount type=<bind/volume> , source=<卷名/宿主机目录> , target=<容器目录> , 镜像名

# Bind Mount 用 --mount
docker run --mount type=bind,source=/my/html,target=/usr/share/nginx/html nginx

# Volume 用 --mount
docker run --mount type=volume,source=nginx,target=/usr/share/nginx/html nginx
#匿名卷省略 source 字段即可
docker run --mount type=volume,target=/usr/share/nginx/html nginx

五、Dockerfile 构建镜像

Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。
基于 Dockerfile 构建镜像可以使用 docker build 命令。docker build 命令中使用 -f 可以指定具体的 Dockerfile 文件。

5.1 第一个 Dockerfile 示例

注意:所有构建镜像的命令必须大写

# 创建目录用于存放 Dockerfile
[root@hd1 ~]# mkdir df
[root@hd1 ~]# cd df

# 编写 Dockerfile
[root@hd1 df]# vim df1
# 设置基础镜像为alpine。这个alpine 是本地已有的镜像(如果有),否则会从网上下载
FROM alpine
# 设置一些标签,解释和说明,没有实际意义
LABEL author=wm
# 运行命令。这些命令必须是你的基础镜像支持的
RUN echo 11111

# 利用 df1 构建镜像
[root@hd1 df]# docker build -t myalpine:v1 -f df1 .
# -t:指定构建镜像的名字
# -f:指定 Dockerfile 文件
# .:在当前目录中进行构建

# 运行镜像
[root@hd1 df]# docker run -t -d --name=ap1 myalpine:v1

# 查看是否在运行(如果不加 -t,容器会立刻退出,因为入口命令为 sh,需要一个伪终端)
[root@hd1 df]# docker ps | grep ap1
踩坑记录

在执行docker build的时候,忘记使用 -t 指定镜像名了,但他并没有报错,结果docker images查看的时候显示为:

REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
<none>       <none>    a1b2c3d4e5f6   5 seconds ago   5.6MB

没有仓库名 (Repository) 也没有标签 (Tag),是一个 “悬空镜像” (dangling image),无法通过 docker run 或 docker push 等命令直接引用这个镜像

5.2 Dockerfile 指令详解

指令说明备注
FROM基础镜像,必须是可以下载下来的(或者本地存在),定制的镜像都是基于 FROM 的镜像必需
LABEL指定镜像的作者信息可选
RUN指定在当前镜像构建过程中要运行的命令功能的核心,可有多条
EXPOSE仅仅只是声明端口(无实际作用)可选
CMD容器运行时的入口命令,可被 docker run 命令行参数覆盖(比如docker run 启动容器结尾的/bin/bash)一般放在文件最后,可有多行,但只有最后一条生效
ENTRYPOINT容器运行时的入口命令,不被 docker run 参数覆盖只有最后一条生效
COPY从宿主机复制文件或目录到容器里指定路径官方推荐
ADD复制指令,比 COPY 增加了 tar 包的自动解压和远程文件复制功能功能更强
VOLUME定义匿名数据卷,启动容器时忘记挂载则自动挂载到匿名卷可选
WORKDIR指定工作目录可选
ENV设置环境变量(构建和运行时均有效)可选
ARG构建参数(仅构建时有效)可选

RUN,CMD,ENTRYPOINT使用方式是一致的,都有两种模式

Dockerfile 的模式非常固定:FROMRUNCOPYENTRYPOINT

5.3 RUN 命令详解

RUN 指令指定在当前镜像构建过程中要运行的命令,用于在镜像层中安装软件、创建目录、修改配置等

有两种不同的模式:

1. Shell 模式(最常用)

RUN <command>
# 例:
RUN echo hello

2. Exec 模式(官方推荐)

  • 语法:
    • RUN ["命令", "参数1", "参数2"]
    • RUN ["解释器", "-c", "命令"](更推荐)
      -c 作用是告诉解释器将后面跟着的那个字符串当作“要执行的命令”来处理
RUN ["executable", "param1", "param2"]
# 例:
RUN ["/bin/bash", "-c", "echo hello"]
RUN ["echo", "hello"]

5.4 CMD 命令详解

类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:

  • RUN:在 docker build 构建镜像时运行
  • CMD:在运行容器时运行,一般作为容器的主程序,需要能够长久运行且在前台运行(如ping这种一直作用的命令)。ENTRYPOINT与CMD要求一致

RUN,CMD,ENTRYPOINT使用方式是一致的

# 编写 Dockerfile
[root@hd1 df]# vim df1
FROM alpine
LABEL author=wm
EXPOSE 80   #EXPOSE没有实际作用
RUN echo 1111
CMD cat   # cat 后面没有跟参数,默认是长久命令,且一直占据前端

# 构建镜像
[root@hd1 df]# docker build -t myalpine:v2 -f df1 .

# 运行容器
[root@hd1 df]# docker run -t -d --name=app2 myalpine:v2

# 查看入口命令(COMMAND下就是入口命令)
[root@hd1 df]# docker ps
ID     IMAGE            COMMAND         CREATED        STATUS        NAMES
851    myalpine:v1      "/bin/sh -c cat" 20 seconds ago Up 20 seconds app1

注意: 如果入口不是长久命令,容器启动执行完毕后会自动退出。容器是为了应用而生,应用执行完毕,容器会自杀。

# 修改 Dockerfile,使用短时命令
[root@hd1 df]# cat df1
FROM alpine
LABEL author=wm
RUN echo 1111
CMD ls /   # ls / 是短时命令,执行完毕后容器会自动退出

# 重新构建
[root@hd1 df]# docker build -t myalpine:v2 -f df1 .
[root@hd1 df]# docker run -t -d --name=app2 myalpine:v2

# 查看容器(已经退出)
[root@hd1 df]# docker ps -a

CMD 的重要特性:

多个 CMD 命令以最后一个生效。CMD 指令指定的程序可被 docker run 命令行参数覆盖。

[root@hd1 df]# cat df1
FROM alpine
LABEL author=wm
RUN echo 1111
CMD ["cat"]

#打包镜像
[root@hd1 df]# docker build -t myalpine:v3 -f df1 .
#守护进程方式运行容器
[root@hd1 df]# docker run -t -d --name=app3 myalpine:v3
# COMMAND 显示为 "cat"
[root@hd1 df]# docker ps   

# 用 ping 127.0.0.1 覆盖 cat 命令
[root@hd1 df]# docker run -t -d --name=app30 myalpine:v3 ping 127.0.0.1
# COMMAND 显示为 "ping 127.0.0.1"
[root@hd1 df]# docker ps

#持续查看容器日志
[root@hd1 df]# docker logs app30 -f

5.5 ENTRY POINT 命令详解

类似于 CMD 指令,充作容器入口指令,但其不会被 docker run 的命令行参数所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。

注意:

  • CMD 和 ENTRYPOINT 最好不要一起使用
  • 如果一起使用,CMD 只能作为 ENTRYPOINT 的参数
  • 如果 Dockerfile 中存在多个 ENTRYPOINT 指令,仅最后一个生效

命令格式:

ENTRYPOINT ["executable", "param1", "param2"]   # 推荐格式
[root@hd1 df]# vim df1
FROM alpine
LABEL author=wm
RUN echo 1111
ENTRYPOINT ["cat"]

[root@hd1 df]# docker build -t myalpine:v5 -f df1 .
[root@hd1 df]# docker run -t -d --name=app5 myalpine:v5
[root@hd1 df]# docker ps   # COMMAND 显示为 "cat"

CMD 作为 ENTRYPOINT 的参数:

CMD命令和ENTRYPOINT命令最好不要一起使用,如果一起使用的话,CMD命令只能作为ENTRYPOINT命令的参数

[root@hd1 df]# cat df1
FROM alpine
LABEL author=wm
RUN echo 1111
CMD ["127.0.0.1"]
ENTRYPOINT ["ping"]

#打镜像包
[root@hd1 df]# docker build -t myalpine:v6 -f df1 .
#运行容器
[root@hd1 df]# docker run -t -d --name=app6 myalpine:v6
[root@hd1 df]# docker ps   # COMMAND 显示为 "ping 127.0.0.1"

# 查看日志验证
[root@hd1 df]# docker logs app6 -f
64 bytes from 127.0.0.1: seq=51 ttl=64 time=0.041 ms
64 bytes from 127.0.0.1: seq=52 ttl=64 time=0.106 ms

5.6 COPY 和 ADD 命令

COPY

复制指令,从宿主机复制文件或目录到容器里指定路径。

格式:COPY [<src> <dest>]

  • <src源路径>:源文件或源目录,可以是通配符表达式
  • <dest目标路径>:容器内的指定路径,路径不存在会自动创建
# 将 services 文件复制到当前目录
[root@hd1 df]# cp /etc/services .

# 修改 Dockerfile
[root@hd1 df]# vim df1
FROM alpine
LABEL author=wm
RUN echo 1111
COPY services /
CMD ["127.0.0.1"]
ENTRYPOINT ["ping"]

#构建镜像
[root@hd1 df]# docker build -t myalpine:v7 -f df1 .
#启动容器
[root@hd1 df]# docker run -t -d --name=app7 myalpine:v7
#无需进入容器,直接查看内容
[root@hd1 df]# docker exec -it app6 /bin/sh  ls  /
#可以看到,services文件已经被拷贝到容器根目录之下
bin       etc       lib       mnt       proc      run       services  sys       usr
dev       home      media     opt       root      sbin      srv       tmp       var
ADD

ADD 指令和 COPY 的使用格式一致(同样需求下,官方推荐使用 COPY)。功能类似,不同之处如下:

  • ADD 在执行 <源文件> 为 tar 压缩文件时,会自动复制并解压<目标路径>

  • ADD 还支持远程文件的下载,直接从 URL 下载文件(但极其不推荐!)

    • ADD http://example.com/bigfile.tar.gz /tmp/
  • 因其功能透明性低,可能是复制、可能是解压、可能是下载,所以不建议使用

# 将 nginx.tar.gz 复制到当前目录
[root@hd1 df]# cp /root/nginx.tar.gz .

[root@hd1 df]# vim df1
FROM alpine
LABEL author=wm
RUN echo 1111
ADD nginx.tar.gz /fff/
CMD ["127.0.0.1"]
ENTRYPOINT ["ping"]

[root@hd1 df]# docker build -t myalpine:v8 -f df1 .
[root@hd1 df]# docker run -t -d --name=app8 myalpine:v8
[root@hd1 df]# docker exec -it app8 /bin/sh ls /fff/

5.7 VOLUME:定义匿名数据卷

定义匿名数据卷,在启动容器时如果用户没有挂载数据卷,会自动挂载到匿名卷

作用:

  1. 避免重要的数据因容器重启而丢失(这是非常致命的)
  2. 避免容器不断变大

格式:

VOLUME ["<路径1>", "<路径2>..."]
VOLUME  <路径>

VOLUME 指令只需要指定容器内的目录即可(可指定多个),他会挂载到 /var/lib/docker/volumes/ 目录下的一个长哈希随机ID文件夹

[root@hd1 df]# cat df1
FROM alpine
LABEL author=wm
RUN echo 1111
VOLUME ["/data"]
CMD ["127.0.0.1"]
ENTRYPOINT ["ping"]

5.8 WORKDIR:定义工作目录(了解)

指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在(WORKDIR 指定的工作目录必须是提前创建好的)。

注意: Docker build 构建镜像过程中,每一个 RUN 命令都是新建的一层(不存在上下文关系),只有通过 WORKDIR 创建的目录才会一直存在。

格式:

WORKDIR <工作目录路径>
WORKDIR /path/to/workdir
[root@hd1 df]# cat df1
FROM alpine
LABEL author=wm
RUN echo 1111
WORKDIR /abc
CMD ["127.0.0.1"]
ENTRYPOINT ["ping"]

我们登录进入容器,默认的路径就是/abc

5.9 ENV 和 ARG:环境变量设置(了解)

ENV(构建和运行时均有效)

ENV 设置的环境变量在构建镜像时可用,在容器运行时也有效。

[root@hd1 df]# cat df1
FROM alpine
LABEL author=wm
RUN echo 1111
ENV node=127.0.0.1
RUN echo $node
CMD echo $node

[root@hd1 df]# docker build -t myalpine:v16 -f df1 .

[root@hd1 df]# docker run -t -d --name=app16 myalpine:v16
#在构建镜像时的输出内容就可以显示RUN命令生效

#上述命令入口echo $node 是短时命令,会退出,通过docker logs 来看
[root@hd1 df]# docker logs app16
127.0.0.1
ARG(仅构建时有效)

ARG 设置的环境变量仅对 Dockerfile 内有效,即只有在 docker build 过程中有效,(因为 ARG 变量只存在于构建阶段)构建好的镜像内不存在此环境变量。

[root@hd1 df]# cat df1
FROM alpine
LABEL author=wm
RUN echo 1111
ARG node=127.0.0.1
RUN echo $node
CMD echo $node   # 运行时 $node 为空,无效

六、常见面试题

RUN 和 CMD 的区别

指令执行时机特点
RUN构建镜像时执行可以有多个,没有上下文关系
CMD运行容器时执行可以有多个,但只有最后一条生效

ENTRYPOINT同样在运行容器时执行,可以有多个,但只有最后一条生效

CMD 和 ENTRYPOINT 的异同

相同点:

  • 都是容器运行的入口
  • 要求后面跟着的命令必须能够长久执行

不同点:

  • CMD 可被 docker run 命令行参数覆盖
  • ENTRYPOINT 不被覆盖,命令行参数会被当作参数传给 ENTRYPOINT
  • CMD 可以作为 ENTRYPOINT 的参数(两个命令的格式必须是列表形式 exec)

ADD 和 COPY 的区别

  • ADD 支持 tar 包的自动解压
  • ADD 支持远程文件下载
  • 官方推荐使用 COPY(更透明,更可预测)

七、实用技巧

7.1 统一时区(重要)

默认镜像时间与宿主机相差 8 小时,这是时区差异导致的。生产环境下,时区不一致会导致业务出问题。

有两种方法解决这个问题,二选一即可:

# 方法1:挂载宿主机时区文件
[root@hd1 df]# docker run -t -d -v /etc/localtime:/etc/localtime myalpine:v1

# 方法2:将时区文件拷贝到容器内
[root@hd1 df]# docker cp /usr/share/zoneinfo/Asia/Shanghai app7:/etc/localtime

最佳实践: 如果觉得每次挂载时区文件很麻烦,可以自定义时区后保存为新镜像再使用。

7.2 容器退出时自动删除

# --rm:当容器退出时,自动销毁容器自身
docker run --rm --name=haha -it busybox:V1 /bin/sh

八、核心命令速查表

在 Docker 中,容器 ID容器名(Name) 是等价的标识符,绝大多数对容器操作的命令都同时支持这两种输入方式。
(镜像名和镜像ID也基本如此,但镜像需考虑标签等)

命令用途
docker images查看本地镜像列表
docker pull从仓库拉取镜像
docker load -i导入离线镜像包
docker save -o导出镜像为离线包
docker rmi删除镜像
docker run创建并启动容器
docker ps查看运行中的容器
docker ps -a查看所有容器(含已停止)
docker stop停止容器
docker start启动已停止的容器
docker restart重启容器
docker kill强制杀死容器
docker rm删除容器
docker rm -f强制删除运行中的容器
docker exec -it进入运行中的容器
docker logs查看容器日志
docker inspect查看容器/镜像底层信息
docker commit将容器保存为镜像
docker cp宿主机与容器间复制文件
docker top查看容器内进程
docker stats查看容器资源占用
docker update修改运行中容器的配置
docker volume ls列出数据卷
docker volume prune删除无用数据卷
docker build -t构建镜像
docker history查看镜像构建历史
docker system prune清理未使用的资源

九、Docker 踩坑记录

以下是我在实际操作过程中总结的经验教训

9.1 -p 端口映射默认绑定 0.0.0.0,存在安全风险

现象
执行 docker run -p 33060:3306 mysql,宿主机上任何人都可以通过 公网IP:33060 访问你的数据库,在实验环境还好,但是在真实的生产环境中安全风险极大。

原因
-p 默认绑定到 0.0.0.0(所有网卡)。若只想本机访问,应绑定到 127.0.0.1

正确做法

# 只允许本机访问
docker run -p 127.0.0.1:33060:3306 mysql

9.2 Bind Mount 空目录导致容器内原有文件被“清空”

现象
执行 docker run --name=nginxa -d -p 8000:80 -v /my/html:/usr/share/nginx/html nginx:latest ,访问http://192.168.1.11:8000/却报 403

原因
Bind Mount 是直接覆盖(遮蔽),宿主机空目录会让容器内的 /usr/share/nginx/html 变成空白目录,导致 Nginx 找不到配置。

解决方案

  • 确保宿主机目录非空,或先拷贝一份配置文件进去。
  • 若只是想持久化数据且不希望覆盖,改用 命名卷(Volume),空卷会自动复制容器原内容。

9.3 Alpine 镜像里没有 /bin/bash,只有 /bin/sh

现象
执行 docker exec -it alpine容器 /bin/bash 报错 rpc error: code = 2 exec: "/bin/bash": stat /bin/bash: no such file or directory

原因
Alpine 为了极致小巧,默认不带 Bash,只提供 sh

正确做法

docker exec -it 容器名 /bin/sh

若确实需要 Bash,可在 Dockerfile 中添加 RUN apk add --no-cache bash


9.4 容器内进程不是长期运行的,容器会立刻退出

现象
docker run --name test alpine ls / 之后 docker ps 看不到容器,但 docker ps -a 能看到状态 Exited (0)

原因
容器的生命周期与主进程绑定,ls / 执行完就结束了,容器自然退出。

解决方案

  • 使用长期运行命令,如 pingnginx -g 'daemon off;'tail -f /dev/null(调试用)。
  • 若必须用 Alpine 做调试,可加 -t 并保持交互:
    docker run -it --name test alpine /bin/sh
    

9.5 docker run 时忘记 -t,导致交互式进程瞬间退出

现象
执行 docker run -d alpine /bin/sh,容器启动后立即退出。

原因
/bin/sh 需要伪终端(TTY),没有 -t 时,Shell 无法分配终端而直接退出。
(注:nginxredis 等守护进程不需要 -t,所以不会踩坑)

正确做法

docker run -td --name alpine1 alpine /bin/sh   # -t 和 -d 同时使用

9.6 docker build 未指定 -t 产生悬空镜像,无法直接 run

现象
docker build -f df1 . 成功,但 docker images 中出现 <none>:<none>

后果
无法通过 docker run <镜像名> 引用,只能通过 IMAGE ID 运行(不直观)。

最佳实践
始终加上 -t,哪怕临时测试:

docker build -t mytemp:test -f df1 .

9.7 docker logs 日志文件无限增长,撑爆磁盘

现象
创建业务容器不对日志文件进行限制, /var/lib/docker/containers 下某个容器的日志文件会无限增长。

解决方案

  • 启动容器时限制日志大小:
    docker run --log-opt max-size=10m --log-opt max-file=3 -d nginx
    
  • 或在 /etc/docker/daemon.json 中全局配置:
    {
      "log-driver": "json-file",
      "log-opts": {
        "max-size": "10m",
        "max-file": "3"
      }
    }
    

9.8 数据卷权限问题:容器内用户写不进挂载目录

现象
容器内进程(如 MySQL 以 mysql 用户运行)无法在 Bind Mount 的宿主机目录中创建文件,报 Permission denied的错误。

原因
容器内 UID(如 999)与宿主机目录的属主(如 root)不匹配,且目录权限不足。

解决方案

  • 方案一:挂载前将宿主目录权限改为 777(不推荐,不安全)。
  • 方案二:在 Dockerfile 中创建与宿主机相同 UID 的用户,或使用 --user 参数指定运行用户。
  • 方案三:使用命名卷(Volume),Docker 会自动处理权限(通常以 root 创建,但容器内用户需有写入能力,必要时加 :Z:z SELinux 标签)。

–privileged \ # --privileged:特权容器,容器内使用真正的 root 用户


9.9 latest 标签陷阱:并非总是“最新”

现象
本地已有 nginx:latest(几个月前的),执行 docker pull nginx:latest 发现已是最新(实际有更新但未拉取)。

原因
latest 只是一个普通标签,Docker 不会自动检查远程更新。若本地已有该标签,docker pull 会认为是已存在而跳过(除非强制拉取)。

最佳实践

  • 生产环境务必使用具体版本号,如 nginx:1.25.3
  • 若确实需要最新,先 docker pull 加上 --no-cache 强制刷新。

9.10 COPY/ADD 的源路径是相对于 构建的上下文,而非 Dockerfile 所在目录

现象
终端所在目录:/home/user/project/
Dockerfile 实际位置:/path/to/Dockerfile
我要复制的 app 目录:/path/to/app(和 Dockerfile 在同一个目录下)

在/执行 docker build -f /path/to/Dockerfile ./path/to/Dockerfile的目录下还有:./app,但文件中的 COPY ./app 提示找不到 ./app

原因
docker build 的最后一个参数是构建上下文(.),COPY 的源路径是相对于该上下文的,而不是 Dockerfile 所在目录。即使 -f 指定了其他路径的 Dockerfile,上下文仍由 . 决定。

“构建上下文”指的是 Docker 客户端(Client)会把这个目录下的所有文件(不包括 .dockerignore 排除的)打包成一个压缩包(Tarball),发送给 Docker 服务端(Daemon)去构建镜像

正确做法
把终端 cd 到 Dockerfile 所在的目录,然后再执行 docker build .


9.11 容器内 systemctl / systemd 无法使用

现象
在基于 CentOS 的容器中执行 systemctl start nginx 报错 Failed to get D-Bus connection

原因
容器没有运行 systemd 作为 PID 1,也没有 D-Bus 会话。

解决方案

  • 不使用 systemd,直接运行二进制命令(如 /usr/sbin/nginx)。
  • 若必须使用 systemd,需以特权模式运行并挂载相关文件,但强烈不推荐(违背容器单进程原则)。

9.12 容器时间与宿主机相差 8 小时(时区问题)

现象
容器内 date 显示 UTC 时间,而宿主机是 CST(中国时区)。

解决方案(二选一):

# 1. 挂载宿主机 localtime
docker run -t -d -v /etc/localtime:/etc/localtime myalpine:v1

# 2. 在 Dockerfile 中设置时区(Alpine)
RUN apk add --no-cache tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

笔记前文已有提及


9.13 docker rm -f $(docker ps -aq) 误删所有容器,包括重要的

现象
本想清理测试容器,结果执行了上述命令,所有运行中的容器被强制删除,数据丢失(若未挂载卷)。

教训

  • 操作前先 docker ps 确认当前环境没有需保留的业务镜像。
  • 生产环境慎用 -f,尽量先 stoprm
  • 重要的数据务必挂载到 Volume 或 Bind Mount。

9.14 docker system prune -a 误删未使用的镜像,导致下次构建拉取缓慢

docker system prune -a 是一个磁盘清理命令,但它的作用范围非常广,是一个高危的“大扫除”指令。
它的作用是:删除所有未被使用的资源,包括停止的容器、未使用的网络、构建缓存,以及所有未被任何容器引用的镜像(不仅仅是悬空镜像)

现象
执行 docker system prune -a -f 清理后,之前缓存的大量中间层镜像被删除,再次构建时又需要重新下载,浪费时间和流量。

建议

  • 定期清理时使用 docker system prune(不加 -a),只删除悬空镜像和停止的容器。
  • 如需深度清理,评估后再加 -a

9.15 镜像加速器配置错误,docker pull 总是超时

现象
docker pull 卡住或报错 net/http: request canceled

排查

  • 检查 /etc/docker/daemon.jsonregistry-mirrors 的地址是否可用(可访问 https://docker.m.daocloud.io/v2/ 测试)。
  • 修改后必须执行 systemctl daemon-reload && systemctl restart docker 生效。
  • 可以临时切换其他公共加速源(如 dockerproxy.com 等)。

更多推荐