由于之前四月份Docker学的不是很扎实,导致直至前两天,对Docker这些相关概念以及实战还是一头雾水,于是这两天经过学习总结一下Docker,把Docker彻底学懂学扎实才放心
大家可以去支持原视频作者
基础概念
学习Docker我们需要需要知道的基础概念名词不是特别多,接下来我们一起看看
镜像
这是Docker最重要的概念,可以把他类比成"安装包"
回想我们的JAVA项目,我们完成了我们的项目,想要上传或者给别人使用,那么我们通常会打包,把我们的项目打成一个jar包给别人
镜像也是同jar包一样,他就是"程序的安装包"
容器
我们刚刚谈过了"镜像"这个概念,那么"容器"也就好理解了,容器就是"可以运行的镜像",也就是说"容器"就是一个可以跑的项目。
还是拿我们之前说"镜像是安装包"的例子来说,既然镜像是"安装包",那么容器就是可以使用的"应用程序"
一个镜像可以构建出多个"容器", 这句话也同样非常好理解:就好比开发者在应用市场上传了自己产品的安装包(镜像),那么大家都可以从应用市场上面下载安装包然后安装到自己手机上去运行(容器)
宿主机
了解完上面的两个概念后,才能了解接下来的这个概念
如果大家之前有安装过Linux虚拟机在自己的电脑上,那么一定对这个概念非常了解。Linux虚拟机作为"虚拟主机"在我们的电脑上运行,它的操作系统以及文件、网络全部都是与我们自己电脑完全独立开的,你无法从Linux虚拟机上访问我们电脑自己的文件,并且如果你想要启动Linux虚拟机就得启动我们自己的电脑,再去打开虚拟机,像这种类似于Linux"寄宿"在我们电脑上,我们的电脑就叫"宿主机"
容器同理,只不过容器它是基于虚拟机Linux运行的,也就是说容器"寄宿"在linux上,所以对于容器来说,它的宿主机就是虚拟机Linux
本质来说:某一个东西的内核在跑另一个东西,那这个"某一个东西"就是"另一个东西"的宿主机
Windows(我们的电脑) 跑 虚拟机====>Windows是 该虚拟机的 "宿主机"
虚拟机 跑 容器 ====> 虚拟机是容器的 "宿主机"

用一张图片展示更加便利:

这张图片也同时展示运行的容器(比如图片中的nginx,监听80端口)我们通过宿主机(Linux)去直接访问80端口是完全无法访问的,因为他们虚拟机和宿主机之间的网络是完全独立的,**如果需要访问,就需要"映射"**不过这都是后话了
下载镜像docker pull
前面已经介绍个"镜像"的相关概念,接下来我们顺着"镜像"这个概念,介绍它相关的重要概念
对于镜像(安装包)我们必须要知道如何下载应用,因此我们来介绍下载镜像的相关命令:
我们先来看完整的一句命令
其中docker pull就是"下载镜像"的关键,其余的我们就接下来一一讲解
registry:仓库地址
docker pull紧随其后的参数就是仓库地址(或者叫注册表registry),可以比作"应用市场",表示我们要从哪个应用市场上下载东西

其中,docker.io是Docker官方的应用市场(仓库地址),如果你要从上面下载东西,可以省略该参数,官方仓库是可以省略仓库地址的:
官方仓库:DockerHub
我们刚刚提到docker.io是官方仓库,我们可以通过命令行去从上面下载镜像,同时我们也可以访问官方仓库的网址https://hub.docker.com/ (记得开加速器/梯子)来对其进行访问,可以直观的看到上面有什么样的镜像

命名空间:namespace(发布者/作者名)
这个很好理解,就是发布镜像的作者名,应用市场上面很多同名应用,如果不加作者名,很难区分这些同名应用,因此作者名(命名空间)是必须的

其中library是docker官方的命名空间(也就是说docker官方是该镜像的作者),同样也可以省略:
镜像名
显而易见,其实就是应用名词,这里就不过多赘述
tag:标签(版本号)
镜像名后面跟着的参数就是该镜像的版本号,每个应用都有相应的版本,这很好理解

我们可以下载指定版本的镜像,如果后面直接写latest或者不写,那就代表获取最新的镜像
镜像其他操作
下载完成镜像,如果想查看或者删除镜像我们要怎么做呢
查看镜像:docker image
我们可以使用docker image来查看我们所有下载过的镜像

删除镜像:docker rmi
docker rmi是通用命令rm--remove的衍生,其实就是remove image移除镜像
rmi后可以跟相关的"镜像名"或者"镜像ID"来对指定镜像进行删除
注意:如果直接删除一个正在跑容器的镜像是无法删除的,必须要停止正在运行的容器,再删除相关镜像,下图这里我想通过ID删除镜像,但是因为容器正在运行就没有删除
对于这种正在运行的情况,我们必须要先停止容器的运行,然后再删除容器,最后才能把这个镜像删除。也就是说对于一个正在运行的应用,我们得想把这个应用停掉,然后删除应用,最后才能把他的安装包彻底删除:
docker stop ai-frontend-test # 1. 停容器
docker rm ai-frontend-test # 2. 删容器(断开它对镜像的引用)
docker rmi e1dd9e8016d2 # 3. 现在才能删镜像
运行容器:docker run
刚刚讲完了镜像相关操作,那么它的容器也是同样重要,接下来我们来讲容器常见的操作
docker run是关于容器最常见的命令操作,它可以把我们所构建好的镜像"跑"成容器,也就是"从镜像造一个新容器"
值得注意的是,如果docker发现本地没有我们所说的镜像,那么docker他就会自己执行我们之前的docker pull指令,自己去拉取一份相关镜像,然后再执行docker run去创建运行容器
使用docker run 镜像名/镜像ID 即可:
上图我们注意到,直接使用docker run下面会一直输出日志,导致该控制台一直被占用,我们无法再在该终端发出任何指令,因此,我们一般不会直接使用docker run,我们一般会在docker run的后面加上参数"-d"
容器后台运行"-d"
d就是detach,让我们的容器以"分离模式"在后台运行:
查看容器:docker ps(-a)
正在运行的容器

我们上面也有用到,docker ps可以直接查看"正在运行"的容器
所有容器

ps后面加上参数-a可以查看所有docker 容器,无论是否运行
端口映射"-p"
还记得我们之前提及的"宿主机"概念吗,宿主机和虚拟机之间的网络是完全独立的网络,之间无法互相通信访问,容器运行再虚拟机,我们电脑作为宿主机如果要访问,就得通过"端口映射"的形式
演示前说明(必看!!!)
但是在此之前,我们需要严肃说明以下的演示,以下的演示都是通过Windows去访问容器,但是容器的宿主机并不是Windows,这是因为我们docker做了一次Windows到Linux的转发,真正实现端口映射的是Linux---->容器 
我们再run出容器输入-p 任意本机端口号:容器监听端口号

我们输入:
docker run -p 9178:80 nginx:alpine
接着我们再浏览器中访问localhost:9178即可访问到我们正在虚拟机运行的容器

容器其他常用命令
docker create
我们前文谈及有关于容器的操作都是基于"run"直接从镜像创建一个正在运行的容器,那如果我只想创建一个没有在运行的容器呢?
那我们就可以使用create命令,和前面的run命令一样,后面指定镜像名称即可从该镜像直接创建一个没有正在运行的容器
docker start/stop
刚刚创建好了没有跑的容器,我们可以使用start+容器名称/ID来让其运行起来
同理,正在运行的容器我们也可以使用stop+容器名称/ID让其停止运行



给正在运行的容器"临时附赠命令"exec
若一个容器正在运行中,但是突然想让该容器做一些之前没有要说的操作(临时加一个任务给该容器),则可以使用"exec"命令

挂载卷"-v"(现在新写法是--mount)
演示前说明
挂载卷这个知识点和前文说的"端口映射"有点像,端口映射是"间接访问",挂载卷是"间接操作",我们本次演示也是使用Windows+DockerDesktop的形式,因此和前面"端口映射"的演示前说明一样,真正的操作是Linux和容器之间的操作,我们电脑Windows的相关操作只不过是被docker进行了一次"视图转发"
绑定挂载
我们之前介绍端口映射的"-p",它是把宿主机和容器的端口进行互相映射(或者说"绑定"),那么挂载卷就是把宿主机的目录和容器的文件目录进行了绑定
也就是说,我们在Linux(宿主机)改动挂载的文件,那么容器所绑定的同样会被改变
这种技术是为了"持久化",比如说之前在容器内进行了改动操作,然后又把容器删除,若绑定了挂载卷,那么这些改动就会在宿主机上保留下来,做到持久化
举个例子:
我们先在我们的宿主机上面创立好相关的目录和文件



接着,我们使用挂载卷命令对其容器进行绑定
然后我们访问容器
接着,我们回到宿主机相关文件,改动文件内容
然后再访问容器
可以发现,容器内容同样被改变

命名卷挂载
对比刚才的绑定挂载需要指定我们宿主机的目录,命名卷挂载就比较方便,只需要向docker申请一个存储空间(这个存储空间也叫命名卷 ),然后直接使用命名卷绑定容器内的目录即可
和刚刚绑定挂载的区别仅仅在于"命名卷"还是"自己指定目录"去挂载容器目录

下面我们来实践一下
1.向docker申请命名卷(存储空间)
使用即可创建命名卷(存储空间)
docker volume create 命名卷名
可以使用来查看该命名卷的各项参数
docker volume inspect 命名卷名
其中被框起来的就是命名卷的真实存储目录(++在宿主机下载docker的根目录那边++)

2.挂空的挂载卷给容器目录

我们直接访问,空卷首次挂载,Docker 把镜像里 /usr/share/nginx/html 的内容复印进卷里了。这是命名卷和绑定挂载行为差异最大的一点(绑定挂载若是初次挂载,则不会吧容器目录相关内容复制到卷中)

然后我们改变容器内容

再访问挂载卷,发现挂载卷内容同样被改变

综上就是命名卷挂载的流程
命名卷性质
我们把之前容器删除,然后创立一个新的容器并且绑定了之前的mmj01挂载卷
可以看到,并不是一开始的welcome to nginx,而是之前保存过的内容

这就是命名卷挂载的性质:只在空卷首次挂载时才复印原本容器初始内容,若挂载卷里已有数据就不动它
有关run的其他常用参数
环境变量设置"-e"
这个很好理解了,就是单纯的设置系统环境变量,和我们平常开发设置环境变量的道理是一样
这里在run容器的时候加上参数"-e"后面再跟上环境变量即可

自定义容器名字"--name"
同样,在run容器的时候后面加上"--name"参数再加你想设立的容器名字,就可以为自己跑镜像的容器自定义名字了(若没有--name参数,docker会自己随机分配名字给刚刚的容器 )

调试容器"-it""--rm"
docker中,我们常创建一个临时的容器用于调试
run容器的时候,我们使用**"-it"可以让我们的控制台进入容器内部,便于调试容器**
而**"--rm"指的是当容器停止运行时,容器自动删除自己**

进入容器内部会有"/#"的提示,使用ls查看容器内部目录

输入exit退出容器(同时容器停止运行) 因此,容器进而被删除

-it --rm这一套组合参数常用于临时创建容器临时调试
容器停止策略"--restart"
run容器的时候后面添加"--restart"参数可以指定容器"停止"后的重启:
运用场景非常广,当宿主机因为断电或者其他不可抗逆因素导致容器停止运行,那么为了保证服务的运行,就必须让容器自动重启
若后面直接跟着"always",则在任意情况的容器停止后,容器都会尝试重启

若后面跟着"unless-stopped",则除了"人为停止容器",其他情况下若容器停止都会尝试重启

DockerFile--构建镜像
我们的项目编写完成,想要上传给别人使用,那么我们就得打包成镜像的形式,其中DockerFile文件就可以方便docker自动帮我们把项目打包成相关镜像
虽然说现在AI工具可以更具我们的项目一键生成DockerFile文件,但是身为开发者的我们还是需要知道DockerFile文件的基本格式,方便我们自己检查生成的DockerFile文件
基本格式
# 井号是注释
指令 参数
指令 参数
常用指令
指令 作用 类比 FROM基于哪个基础镜像开始造 买房选毛坯房 RUN构建时执行一条命令(装软件等) 装修时施工 COPY把构建上下文里的文件拷进镜像 搬家具进屋 WORKDIR设置后续命令的工作目录 指定在哪个房间施工 EXPOSE声明容器打算监听哪个端口 贴个"此屋是厨房"的标签 CMD容器启动时默认执行的主进程 入住后每天干的第一件事 ENV设置环境变量 写在门上的便签 ENTRYPOINT和 CMD 类似但更难被覆盖(后面细讲) --- 最后需要在"构建上下文"中执行
docker builddocker就会立马读取"上下文目录"中的DockerFile文件,然后构建出相应镜像
这里说一下"COPY"这个指令所讲的"构建上下文"吧:
所谓的构建上下文指的是DockerFile所在的目录
其中"COPY"命令可以拷贝++上下文目录以及衍生出的子目录的所有文件,但是上下文之外的目录就无法拷贝++
简单DockerFile编写上传DockerHub
1.DockerFile编写
了解完DockerFile的基本格式,那么我们完全可以靠AI工具来帮我们生成DockerFile
2.基于DockerFile构建镜像
首先,基于DockerFile构建镜像一定要把目录切换成前面讲的"构建上下文"---也就是存放DockerFile的目录
然后使用docker build命令
参数:
-t:用于声明构造的镜像的的名称,名称后面可以跟":"冒号+数字来标明版本号
. : 用于声明构建的镜像再当前目录构建
3.运行镜像
构建完成后我们就可以来运行我们刚刚的镜像了

注意:镜像标明的版本号也是镜像名称的一部分,不要漏掉
我们访问映射的端口号即可(这里字节编码错误)
4.推送到DockerHub
前面的测试都没问题的话,我们就可以把我们的镜像上传到DockerHub上了
首先上传到云端第一件事情就是登录
输入docker login进行dockerhub的登录

登录完成后,我们需要按照我们的用户名重新基于DockerFile打包镜像
(注意:打包的镜像名前面需要加"/"和你DockerHub的用户名)

打包完成后就可以完成推送了
输入docker push 再加上 刚刚-t后面的参数即可

最后我们可以从DockerHub看到我们上传的镜像,大功告成!

Docker网络
接下来学习docker网络,来学习docker中的容器是如何通过网络来实现互相通信的
注意:"容器之间互相通信"这是后续实战非常常见的情况,举一个简单的例子:
部署完整项目时,后端写数据库的地址是"localhost:xxxx",但是后端和数据库肯定不会部署在同一个容器中(后续会说),容器中说的"localhost"是容器自己,不是我们开发时说的"本机",因此后端就无法通过地址来访问不同容器中的数据库
bridge--桥接模式(默认)
这是docker的默认网络模式--桥接模式:
即我们之前早就提及的宿主机和容器之间的网络时隔离的,无法直接互相通信,但是容器之间默认是共用一个内部网络的,因此容器之间可以通过内部网络(需要知道访问容器的IP地址)来实现通信

子网
若想要容器之间还有独立的网络,我们可以使用
docker network create
来创建出"子网"
子网同样符合"桥接模式bridge"类型网络,无法和宿主机网络相互通信,同时,只有在同一个子网中的容器才能通过网络互相通信,不同子网的容器无法互相通信

名字访问容器特性
我们前文提及桥接模式下容器之间互相通过内部网络互相访问是需要知道访问的容器的IP地址的(比如上图的"172.18.0.2")。
但是如果是子网中的容器想通过网络实现互相通信,则不需要知道IP地址,只需要知道想要访问的容器名称即可

举个例子:
这里创建运行基于MongoDB数据库镜像的容器,来运行基础的MongoDB数据库的服务
接着来运行MongoDB的可视化网页(网页本身没有数据库能力,只是用于可视化)
注意标注地方:在环境变量中传入了想要访问的容器名称(就是我们上面所创建的MongoDB容器名称)

最后通过端口映射来访问可视化网页,发现有数据库的能力,这说明"可视化网页"可以与"MongoDB"容器来进行通信

Host模式
前文我们总是讲宿主机网络和docker容器内网络是隔离的,但是那都是默认情况下(桥接模式),当我们把docker网络切换为"Host"模式,网络情况将会变为:Docker容器直接共享宿主机的网络,容器直接使用宿主机的IP地址
这意味着:我们运行容器不需要像前文那样进行"-p"的端口映射了,直接使用宿主机网络(宿主机的IP和端口)就可以直接访问容器
我们直接访问本机地址80端口,便可以直接访问到容器



None模式
这个就简单多了:容器完全没有网络,只有一堵墙里的单机。用途是跑那些"只需要算数、不需要联网"的任务------比如拿容器做数据批处理、跑个计算脚本,断网反而是安全保障

DockerCompose--容器编排技术
学习该章节内容请先思考一个问题:一个基本的项目一般由前后端+数据库三部分组成,如果需要把整一个项目使用容器的方式运行,要怎么运行?
如果我们一股脑的把这三部分放到同一个容器运行,其中只要有一个部分出现问题,那么我们就需要停掉整一个部分,然后修改错误再去重新运行整一个部分,这是非常繁琐并且维护成本非常高的方式。
正确方式应该是按照项目组成的部分去规划不同的容器运行,我们刚刚举例的项目一共三个部分,那么我们用三个容器去运行这三个部分,如果由一个部分出现了问题,我们只需要停止出问题的那个部分加以修正即可,不需要像前面那样整一个去修改。
但是直接创建三个容器"使用成本"就非常高,像上图一样,我们每一个容器都要执行比较长的"docker run"命令,并且三个容器之间的网络配置也非常难配置
这个时候,我们就可以使用"容器编排技术"--DockerCompose
DockerCompose使用yml文件来创建管理多个容器,yml配置文件声明了容器是如何创建以及如何协同工作的,我们可以简单的认为DockerCompose文件就是把多个docker run命令写到一起
结合我们之前学过的"DockerFile",他们在功能上是相似的,都是把一堆命令弄到一个文件,最后直接识别该文件进行执行,只不过DockerFile是针对"镜像",而DockerCompose是针对"整个项目或者容器"

我们来看看一份标准的dockercompose的yml文件长啥样:
services: # 这个项目有哪些"服务"(≈容器)
app: # 服务名,其实就是"容器名"
build: . # 用当前目录的 Dockerfile 现场构建
ports:
- "8080:8080" # 等价于 -p 8080:8080
environment: # 等价于 -e,配置外置化就写在这
- DB_URL=jdbc:mysql://mysql:3306/medical
depends_on:
- mysql # 先起 mysql 再起我
mysql:
image: mysql:8.0 #image=镜像
volumes:
- mysql-data:/var/lib/mysql # 命名卷
environment:
- MYSQL_ROOT_PASSWORD=xxx
volumes: # 声明用到的命名卷
mysql-data:
我们来看简单的对照

值得注意的是:
1.使用dockercompose去run容器不用自己设置子网,dockercompose会自动设置子网
2.dockercompose可以指定容器的启动顺序,使用depends_on即可指定该容器会"依赖于"哪个容器,则"被依赖的容器"会先在该容器前启动
实战--Docker部署
接下来我们正式进入Docker部署的实战,我们随便拿一个项目来实战
1.打包确认,确认项目无问题
在docker容器化部署前,我们需要先打包确认我们源项目是否有问题
有同学可能会有和我一样的疑问:
为什么不能直接在编译器idea里面运行而是要打包出来运行呢?
IDEA 里点运行 = 后厨现炒 ↳ 依赖你电脑上的 JDK、Maven 仓库、源码、IDEA 本身,现编译现跑 jar 包 = 封好的罐头 ↳ 编译产物+所有依赖全部封进一个文件,自带 Tomcat,自包含关键在于:Docker 镜像里没有 IDEA、没有 Maven、没有你的源码。镜像里只有一个 JRE(Java 运行环境),它唯一会做的事情是:
java -jar 某个jar包所以镜像里必须放的是"罐头",不是"后厨"。企业里所有 Java 项目的 Docker 化都是这个流程:先在构建机上打包成 jar → 再把 jar 塞进镜像 。这也是为什么 Dockerfile 里是
COPY jar而不是COPY 源码(进阶的多阶段构建才在镜像里编译,我们先用主流简单路线)。
打包完成后启动项目
检测通过后即可
2.编写DockerFile
在如今AI时代下,我们只需要让Agent工具基于我们的项目写DockerFile文件即可,我们只需要管容器部署的事情

3.单容器试运行(后端)
注意:该步骤是为了检查"容器网络"有无问题:容器网络陷阱(容器里的 localhost 够不到本机 MySQL,要用 host.docker.internal)。此时 jar 合格(0 验过)、镜像合格(2 验过)、配置可注入(1 备好),所以一旦连不上数据库,凶手只可能是网络地址这一件事
先切换到含有DockerFile的目录,根据DockerFile构建出镜像
docker build -f docker\backend.Dockerfile -t medical-app:1.0 .
run出容器
(这里注意要先把之前jar包跑的程序先停掉,会端口占用导致只会create容器,容器不会跑起来)
docker run -d --name medical-app -p 3456:3456 `
-e DB_URL="jdbc:mysql://host.docker.internal:3306/medical" `
-e DEEPSEEK_API_KEY="$env:DEEPSEEK_API_KEY" `
-e OSS_ACCESS_KEY_ID="$env:OSS_ACCESS_KEY_ID" `
-e OSS_ACCESS_KEY_SECRET="$env:OSS_ACCESS_KEY_SECRET" `
medical-app:1.0
接着开后端验证即可
4.MySQL容器化(双容器测试运行)
到这一步我们的docker容器化开始双容器测试,之前我们都是在做前面的准备工作(包括上一步容器化的后端也是。还有一个容易忽略的点:3 和 4 不是重复劳动。阶段 3 本身就是一个真实的"过渡形态"------老系统 Docker 化改造的常规第一站就是"应用先进容器,数据库照旧用现有的",验证稳了再决定数据库要不要搬。
4.1拉取MySQL镜像

4.2创建挂载卷
算是常规操作了,我们之前学习挂载卷的时候有提及,像这种数据库的挂载我们就用命名卷挂载的方式
4.3运行MySQL容器

4.3.后端容器运行
之前我们试运行了后端容器,测试的没问题我们就可以正式启用后端容器了
(这一步在MySQL容器后面,原因我们前文有提及:后端服务依赖数据库,得先让数据库启动)
正式运行后端容器我们先把之前测试用的后端容器删除

4.3.1运行后端容器
输入相关指令run出后端容器

同时可以让其持续输出日志

5.Compose一键编排容器
前面的单容器双容器我们都验证通过了,接下来我们就使用前文所讲的DockerCompose一键编排前后端+数据库容器
先把前面占用端口的容器删除,验证没问题了就让AI工具帮我们生成用于compose的yml文件吧!


部署成功




