【工具】Docker 的基础使用
一、安装 Docker
翻阅 Docker 官网文档说明:


以 Ubuntu 为例:

查看依赖:
# 查看 cpu 架构
uname -a# 查看操作系统版本
cat /etc/*release*



1、卸载旧的 docker(没安装过的忽略)
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done
卸载 Docker 时,存储在 /var/lib/docker/ 中的镜像、容器、卷和网络不会不会被自动删除。如果希望全新安装并清理所有现有数据,执行以下步骤。
卸载 Docker Engine、CLI、containerd 和 Docker Compose 软件包:
sudo apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras
删除所有镜像、容器和卷:
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd
删除源列表和密钥:
sudo rm /etc/apt/sources.list.d/docker.list
sudo rm /etc/apt/keyrings/docker.asc
2、配置 docker 下载源
添加 Docker 官方 GPG 密钥:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
配置 Docker 的下载仓库:
echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
3、安装 Docker 包
安装最新版:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
4、启动 docker
// 启动 docker
sudo systemctl start docker// 停止 docker
sudo systemctl stop docker// 重启 docker
sudo systemctl restart docker// 设置开启时自动启动
sudo systemctl enable docker// 查看 docker 服务状态
sudo systemctl status docker// 查看 docker 版本
docker -v
查看一下版本、配置开机自动启动、启动,然后查看一下 docker 状态。
5、配置镜像加速
镜像下载默认是从国外仓库 Docker Hub 下载的速度比较慢,我们需要配置镜像加速(在本地 Docker Hub 仓库服务器之间设立国内的缓存节点服务器)。
查找可用镜像源:https://github.com/dongyubin/DockerHub
sudo mkdir -p /etc/docker # 创建文件夹
sudo tee /etc/docker/daemon.json <<EOF # 创建文件
{ # 镜像加速仓库配置"registry-mirrors": ["https://docker.1panel.live","https://docker.1ms.run","https://dytt.online","https://docker-0.unsee.tech","https://lispy.org","https://docker.xiaogenban1993.com","https://666860.xyz","https://hub.rat.dev","https://docker.m.daocloud.io","https://demo.52013120.xyz","https://proxy.vvvv.ee","https://registry.cyou"]
}
EOF
sudo systemctl daemon-reload # 加载配置
sudo systemctl restart docker # 重启 docker
二、案例1:部署 mysql
1、创建并运行容器
可以去 Docker Hub 官网搜索:

docker run -d \ # 创建并后台运行一个容器
--name mysql \ # 容器名
-p 3307:3306 \ # 在服务器上端口号: 在容器上的端口号
-e MYSQL_ROOT_PASSWORD=123456 \ # 配置 root 用户密码
-e TZ=Asia/Shanghai \ # 校准时区
mysql:tag # 从仓库下载的镜像名,tag 镜像是版本号,如果不写就默认下载学新的版本docker run -d \
--name mysql \
-p 3307:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
-e TZ=Asia/Shanghai \
mysql

为什么要配置云服务器和容器端口的映射:我们从外面访问云服务器上容器里的 mysql,是无法直接访问容器的 IP(像是内网),只能先访问云服务器,然后云服务器在与其本地运行的容器进行连接,外部就能够与容器间接通信了。这也让容器之间互不影响,完全隔离于服务器。

过程:守护进程监听到 run 命令,检查本地有没有对应镜像,没有就从镜像仓库下载到本地仓库。之后创建新的容器都可以从本地获取。

2、概念总结
docker 是啥:下载镜像、创建并运行容器,实现快速部署。
镜像是啥:把依赖、配置、系统函数库一起打包。相当于qq安装好后的文件,直接把文件打包,因为包含有系统函数库,所以镜像是可以跨平台的。
容器是啥:为每个镜像进程的环境用容器隔离起来。
三、常用命令
主要是参考 Docker docs 文档:docker | Docker Docs

不知道详细参数怎么使用的,可以用 --help 看说明,如 docker run --help:

一张图学懂最常用的 docker 命令:

// 查看正在运行的容器
docker ps// 查看所有容器(包括已停止的)
docker ps -a// 还可以格式化容器详情
docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Ports}}\t{{.Status}}'// 停止容器
docker stop 容器名/容器ID// 启动已停止的容器
docker start 容器名/容器ID// 删除已停止的容器
docker rm [-f 强制删除运行中的容器] 容器名/容器ID// 查看镜像
docker image// 删除本地镜像
docker rmi <镜像ID或镜像名:标签>// 进入运行的容器,然后正常使用
docker exec -it <容器名或容器ID> bash// 退出容器
exit
四、数据挂载
1、案例2:利用 Nginx 容器部署静态资源
我们需要修改 nginx 中的 index.html 文件,因此需要进入 docker 容器,但是发现像 vi、ls 等操作系统命令根本无法使用,这是因为镜像只包含了使用 nginx 必备系统函数等,vi、ls 并不是 nginx 必备的,所以我们不能在 docker 容器中直接修改。
数据卷登场,本地路径映射到虚拟数据卷,数据卷再映射到容器路径,其中一个路径文件发生改变,另一个路径文件内容也会改变。

2、数据卷常见命令
数据卷常见命令,不用记,直接 docker volume --help 查询:
# 创建数据卷
docker volume create# 查看所有数据卷
docker volume ls# 删除指定数据卷
docker volume rm# 查看某个数据卷的详情
docker volume inspect# 清除数据卷
docker volume prune
实际上,在 docker run 时,会自动为容器创建一个数据卷(名字是很长的随机字符串,建议指定);我们可以指定数据卷名:-v 数据卷名:容器内目录

docker volume inspect 可以查看到数据卷映射到本地的路径,当容器删了这个数据卷路径还会保留,便于持久化:

docker inspect 可以查看本地路径和容器路径:

3、本地修改容器中的文件
先重新创建、运行一个 nginx 容器,记得指定数据卷:
# :ro 设置容器内只读,防止容器内进程意外修改网页文件
docker run --name nginx \
-v nginx:/usr/share/nginx/html:ro \
-p 80:80 \
-d nginx
本地进入这里面修改:

4、案例3:mysql 容器的数据挂载
基于宿主机目录实现MySQL数据目录、配置文件、初始化脚本的挂载。需要查看 Docker Hub 官方文档:mysql - Official Image | Docker Hub



这个时候映射的文件太多,我们指定数据卷,需要到它生成的目录下把配置文件、sql 脚本放进去,不方便,我们更希望放到我们自己指定的路径:- v 本地目录:容器目录
docker run -d \
--name mysql \
-p 3307:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
-e TZ=Asia/Shanghai \
-v /root/mysql/data:/var/lib/mysql \
-v /root/mysql/init:/docker-entrypoint-initdb.d \
-v /root/mysql/conf:/etc/mysql/conf.d \
mysql
在执行命令之前,先把配置文件放到 conf、把 sql 脚本放到 init 文件,这样就能做到数据迁移(为了更新 mysql 版本)。
五、自定义镜像 Dockerfile
1、概念
我们把自己的应用程序打包成镜像,就需要用 dockerfile 脚本。部署一个程序的过程:准备一个 Linux 环境、安装 jre、redis、mq 等并配置环境变量、复制应用程序的 jar 包,编写脚本运行 jar 包。这每个环节都分为一层打包成单独的文件,便于复用(因为很多应用程序都可以复用一套 Linux 环境和 jre 等)。
dockerfile 就是指令的集合,参考官方文档:Dockerfile reference | Docker Docs
但是我们只需要了解常见的,因为基础的镜像别人也做好了,我们直接使用就行,不用那么复杂。
- FROM:指定基础镜像
- ENV:设置环境变量,可在后面指令使用
- COPY:拷贝本地文件到镜像的指定目录
- RUN:执行Linux的shell命令,一般是安装过程的命令
- EXPOSE:指定容器运行时监听的端口,是给镜像使用者看的
- ENTRYPOINT:镜像中应用的启动命令,容器运行时调用
示例:
# 指定基础镜像(操作系统环境)
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录、容器内时区
ENV JAVA_DIR=/usr/local
# 拷贝本地 jdk 和 java 项目的包到 容器内的路径
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /app/app.jar
# 安装JDK 把容器内路径下的 jdk 解压到另一个路径
RUN cd $JAVA_DIR \ && tar -xf ./jdk8.tar.gz \ && mv ./jdk1.8.0_144 ./java8
# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
# 入口,java项目的启动命令
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
因为 jdk 又有打包好的镜像,所有可以缩减为:
# 基础镜像
FROM openjdk:11.0-jre-buster
# 拷贝jar包
COPY docker-demo.jar /app/app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
2、案例4:将 demo 程序打包成镜像
Dockerfile:
# 基础镜像
FROM openjdk:11.0-jre-buster# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone# 拷贝jar包
COPY docker-demo.jar /app.jar # 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
准备 jar、dockerfile 到服务器上:
![]()
构建镜像:
# -t 给镜像起名(需要小写) 镜像名:tag
# . 表示执行当前目录下的 Dockerfile(如果不是这个名字,要指定文件名)
docker build -t my-image:1.0 .

创建并运行容器:

六、网络
看一下 mysql、nginx 两个容器在容器内的 IP:


看一下服务器上的网卡,有一个 docker0 的虚拟网卡,它是连接各个容器的网桥,各个容器有同一个网关地址:172.17.0.1/16,只要是以 172.17 开头的 ip 都在一个局域网内。

因此,nginx 和 mysql 两个容器是能够互相通信的。但是局域网内的 ip 是随机分配的,某个容器不会固定 ip,每次重启都有可能变。因此我们需要自定义网络(以容器名加入网络,容器名可以固定不变):docker network | Docker Docs
- docker network create:创建一个网络
- docker network ls:查看所有网络
- docker network rm:删除指定网络
- docker network prune:清除未使用的网络
- docker network connect:使指定容器连接加入某网络
- docker network disconnect:使指定容器连接离开某网络
- docker network inspect:查看网络详细信息
我们创建一个自定义网络,并让 mysql、nginx 容器连上它:
# 创建网络
docker network create my-network# 让 mysql 加入网络(先创建容器,再加入自定义网络,会先加入 docker0 默认网卡)
docker network connect my-network mysql# 也可以在创建时就加入自定义网络(创建容器时就加入,不会加入 docker0 默认网卡)
docker run --network my-network

七、项目部署
1、部署 Java 应用

Dockerfile:
# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包
COPY hm-service.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
放到Linux服务器里打包成镜像:
![]()

创建并运行应用程序的容器,并加入自定义网络:

2、部署前端
nginx 需要对前端目录进行代理,那么需要编写配置文件。静态资源在nginx容器内的地址:

所以配置文件需要指明路径:
worker_processes 1;events {worker_connections 1024;
}http {include mime.types;default_type application/json;sendfile on;keepalive_timeout 65;server {# 用户静态资源,通过 18080 端口访问listen 18080;# 所有以 / 开头的请求都回到这个目录下获取静态资源location / {root /usr/share/nginx/html/hmall-portal;}# 错误状态码,会自动返回默认 html 路径下的 /50x.html 页面error_page 500 502 503 504 /50x.html;location = /50x.html {root html;}# 前端页面发来的,所有以 /api 开头的请求,都会被重写(去掉/api),然后访问 hall-app 容器的 8080 端口location /api {rewrite /api/(.*) /$1 break;proxy_pass http://hall-app:8080;}}server {listen 18081;# 指定前端项目所在的位置location / {root /usr/share/nginx/html/hmall-admin;}error_page 500 502 503 504 /50x.html;location = /50x.html {root html;}location /api {rewrite /api/(.*) /$1 break;proxy_pass http://hall-app:8080;}}
}
然后重新创建 nginx 容器,需要挂在 config、html 的路径:

把文件放到 nginx 容器映射到的本地目录:
![]()
创建并运行容器:
docker run -d \
--name nginx \
-p 18080:18080 \
-p 18081:18081 \
-v /root/nginx/html:/usr/share/nginx/html \
-v /root/nginx/nginx.conf:/etc/nginx/nginx.conf \
--network my-network \ # 需要跟后端 jar 包在同一个网络
nginx
访问网页:部署成功,4000 端口对应的 18080,我这里是设置了 xshell 的隧道,就不用打开防火墙,仅 xshell 远程连接的本地能访问。

3、DockerCompose
我们发现,部署一个项目需要挨个创建并运行多个容器,包括前端、后端、mysql、各种中间件....,这个流程非常繁琐,并且容易出错(比如忘记把容器连接到同一个网络,忘记配置映射等)。如果每一次都手动执行这个流程,耗费精力还容易出错。所以我们需要 DockerCompose,只需要写一次 yml 文件,然后按照 DockerCompose 的配置放置 Dockerfile 等文件到服务器指定目录,后续就能一键部署和删除,非常方便。
DockerCompose 的编写只不过把 docker 的命令,比如 docker run、docker build 改成它的语法即可。
把上面的分别部署,改为 docker-compose.yml:
version: "3.8" # Docker Compose 的语法版本services: # 服务定义mysql:image: mysql # 镜像名container_name: mysql # 容器名ports:- "3306:3306" # 端口映射environment:TZ: Asia/ShanghaiMYSQL_ROOT_PASSWORD: 123volumes:- "./mysql/conf:/etc/mysql/conf.d"- "./mysql/data:/var/lib/mysql"- "./mysql/init:/docker-entrypoint-initdb.d"networks:- hm-nethmall: # 如果不指定镜像名称,默认 项目名称_hmall(compose 命令参数里的 -p,默认 root)build: context: . # 当前目录下dockerfile: Dockerfile # 的 Dockerfile 文件container_name: hmallports:- "8080:8080"networks:- hm-netdepends_on: # hmall 需要连接 mysql,会先创建 mysql 容器,再创建该容器- mysqlnginx:image: nginxcontainer_name: nginxports:- "18080:18080"- "18081:18081"volumes:- "./nginx/nginx.conf:/etc/nginx/nginx.conf"- "./nginx/html:/usr/share/nginx/html"depends_on:- hmall # 前端静态资源的代理 nginx 需要访问后端networks:- hm-net
networks: # 网络配置hm-net:name: hmall # 定义一个名为 hm-net 的自定义网络,显示名称为 hmall
docker compose 的命令:

八、综合练习-部署自己的项目
把我的 “公司年会抽奖系统” 是用 DockerCompose 一键部署。该系统需要:
- 后端应用(前端静态资源包含在里面,没有进行前后端分离)容器:需要自己制作镜像,编写 Dockerfile;项目发布版配置里面要把 mysql、redis、rabbitmq 的连接 ip 改成容器名;还有注意 docker run 的时候需要映射容器内的 logs(程序生成的日志)、pic(用户上传的奖品图片)文件路径。
- mysql 容器:注意 mysql 用户要新创建一个,它的权限只能操作该系统的数据库、密码要与后端代码配置文件里的匹配。还有 init 放 sql 脚本,运行容器时就创建数据库;config 数据库配置数据库的编码。
- redis 容器
- rabbitmq 容器:还需要安装可用管理平台。
- 最后把这些 docker 命令改写成 DockerCompose。
(1)mysql
- 导出原有的数据库结构+数据,到指定 init 目录下(运行容器时会自动执行):
# 创建目录
mkdir -f /root/mysql/init# 把 lottery_system 数据库导出.sql
# 这里省略了 -uroot -p,因为本地 root 是 auth_socket 认证方式,可以免密登录
mysqldump lottery_system > /root/mysql/init/lottery_system.sql
- 编写 init-user.sql 脚本,创建一个业务用户,只有该数据库的权限,放到本地 init 文件中:
-- 创建专用业务用户
CREATE USER 'lottery-system'@'%' IDENTIFIED BY '密码';
-- 赋予数据库操作权限
GRANT ALL PRIVILEGES ON lottery_system.* TO 'lottery-system'@'%';
-- 刷新
FLUSH PRIVILEGES;
- 编写配置文件,后缀为 .cnf:
[client]
default_character_set=utf8mb4
[mysql]
default_character_set=utf8mb4
[mysqld]
character_set_server=utf8mb4
collation_server=utf8mb4_unicode_ci
- DockerCompose:
services:mysql:image: mysqlcontainer_name: mysqlports:- "3306:3306"environment:TZ: Asia/ShanghaiMYSQL_ROOT_HOST: "localhost" # 关键:只创建 root@'localhost'MYSQL_ROOT_PASSWORD: "Zhamuyan200114@"volumes:- "./mysql/conf:/etc/mysql/conf.d"- "./mysql/data:/var/lib/mysql"- "./mysql/init:/docker-entrypoint-initdb.d"networks:- app-netrestart: unless-stoppednetworks:app-net:name: app-network
(2)redis
redis:image: rediscontainer_name: redisports:- "6379:6379"volumes:- ./redis/conf:/usr/local/etc/redis- ./redis/data:/dataenvironment:TZ: Asia/Shanghainetworks:- app-netrestart: unless-stopped
(3)rabbitmq
rabbitmq:image: rabbitmq:3-management # 带管理界面的版本container_name: rabbitmqhostname: lottery-rabbit # 显式指定主机名(固定节点名,避免数据目录变化:每次重启容器,主机名都是随机的,所以每次数据卷路径都不一样,因此需要指定主机名)ports:- "5672:5672" # 消息通信端口- "15672:15672" # 管理界面端口environment:TZ: Asia/ShanghaiRABBITMQ_DEFAULT_USER: "lottery_admin" # 自定义用户名(guest 只能访问 / 虚拟主机)RABBITMQ_DEFAULT_PASS: "admin" # 自定义密码RABBITMQ_DEFAULT_VHOST: "lottery_vhost" # 自定义虚拟主机,相当于不同数据库volumes:- ./rabbitmq/data:/var/lib/rabbitmq- ./rabbitmq/conf:/etc/rabbitmqnetworks:- app-netrestart: unless-stopped
(4)应用程序
app:build: context: .dockerfile: Dockerfilecontainer_name: lottery-appports:- "8080:8080"volumes:- ./lottery-app/logs:/app/logs # 容器内日志目录映射到宿主机- ./lottery-app/picture:/app/picture # 用户上传的奖品图片文件networks:- app-netdepends_on:- mysql- redis- rabbitmqrestart: unless-stopped
Dockerfile:
FROM openjdk:17-jdk-alpine# 时区变量
ENV TZ=Asia/Shanghai
# Java 以无头模式运行,避免显示设备依赖(与安装字体一起使用)
ENV JAVA_OPTS="-Djava.awt.headless=true"WORKDIR /app# 安装时区数据、fontconfig 和 ttf 字体(解决 SunFontManager 初始化失败的问题)
RUN apk add --no-cache \tzdata \fontconfig \ttf-dejavu \&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \&& echo $TZ > /etc/timezone# 复制应用 jar
COPY lottery-system-0.0.1-SNAPSHOT.jar ./lottery-system.jar# 使用 shell 形式以展开 JAVA_OPTS
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/lottery-system.jar"]
当前目录:

一键部署:最好指定哪个项目 -p,不然可能查不出来;查日志的时候注意不是容器名,而是服务名。
# 启动 -p 项目命名 -f 指定 dockercompose 文件路径 -d 后台运行
docker compose -p lottery -f docker-compose.yml up -d# 查看运行中的容器
docker compose -p lottery ps# 查看【服务 SERVICE】的日志
docker compose -p lottery logs app# 删除
docker compose -p lottery down

