Dockerfile详解

10829 字
54 分钟
Dockerfile详解

Dockerfile详解#

[TOC]


📋 Dockerfile 核心指令速查表#

执行顺序指令重要程度简要介绍
1FROM⭐⭐⭐⭐⭐==指定基础镜像==,必须是第一句(ARG除外)
2ARG⭐⭐⭐==定义构建时的变量==,在Dockerfile文件中可用
容器运行后, env命令查找不到该变量
在==构建镜像==时, 可被替换 --build-arg key=value
3LABEL⭐⭐⭐⭐⭐==标记== 给镜像加元数据,比如作者、版本描述,可以添加任意键值对元数据
4WORKDIR⭐⭐⭐⭐⭐==设置工作目录==,后续的 RUNCMD 等指令都在这个目录下执行
5ENV⭐⭐⭐⭐⭐==设置环境变量==,比如 ENV MYPATH=/app,容器运行时也能读到 -> $MYPATH <- Dockerfile也可引用
在==运行容器==时, 可以被替换 -e key=value
6RUN⭐⭐⭐⭐⭐==执行命令==,每执行一次会生成一个新的镜像层
7COPY⭐⭐⭐⭐⭐==复制== 最推荐把本地文件复制到镜像里
8ADD⭐⭐⭐⭐⭐类似 COPY,但能自动解压 tar 包或下载远程 URL 文件
9EXPOSE⭐⭐⭐⭐⭐==声明端口== 告诉 Docker 容器在运行时会监听哪个端口,仅作声明,不自动映射 多个端口空格隔开
10VOLUME⭐⭐⭐⭐⭐==数据卷== 创建一个挂载点,用于数据持久化,防止容器删除后数据丢失
11USER⭐⭐==指定容器中的运行用户==或 UID
容器中必须有对应用户或UID
12HEALTHCHECK⭐⭐⭐==健康检查== 定义命令来检测容器服务是否存活
13ENTRYPOINT⭐⭐⭐⭐⭐配置容器==启动后执行的命令==,且该命令不会被覆盖,适合做固定入口
后面的CMD或者docker run命令作为==参数==传递给它
14CMD⭐⭐⭐⭐⭐==默认命令== 用于容器启动时的默认参数或命令,容易被 docker run 后的参数覆盖
  • 后面扩展指令还有三个 ---> ==共17个==

常用指令#

WORKDIR指定工作目录#

Terminal window
1)-w 手动指定工作目录
✅️ 在运行容器的同时指定工作目录
[root@Docker ~]# docker run -it --name v1 -w /usr/local/bin alpine:3.20.2
/usr/local/bin # pwd
"/usr/local/bin"
2)Dockerfile构建中的WORKDIR
[root@Docker ~]# vim dockerfile-01
FROM alpine:3.20.2
WORKDIR /usr/local/bin
[root@Docker ~]# docker build -f ./dockerfile-01 -t test:1.0 .
[root@Docker ~]# docker run -it --name t01 test:1.0
/usr/local/bin # pwd
"/usr/local/bin"

EXPOSE暴露端口#

  • 多个端口空格隔开
Terminal window
[root@Docker ~]# vim default.conf
server {
listen 80 default_server;
server_name _;
root /code;
location / {
index index.html;
}
}
[root@Docker ~]# vim dockerfile-02
# 这里没有指定暴漏的端口80
FROM alpine:3.20.2
LABEL maintainer="Jiuzhao"
WORKDIR /usr/
RUN apk update && apk add nginx
RUN mkdir /code
COPY ./default.conf /etc/nginx/http.d/
RUN echo "This is my page" > /code/index.html
CMD ["nginx","-g","daemon off;"]
[root@Docker ~]# docker build -f dockerfile-02 -t test:2.0 .
[root@Docker ~]# docker run -d -P --name t01 test:2.0
'随机暴露容器端口' -P(大写)
[root@Docker ~]# docker ps
IMAGE COMMAND PORTS NAMES
test:2.0 "nginx -g 'daemon of…" t01'
✅️ 在构建镜像的时候,没有EXPOSE暴漏出来端口
✅️ 这里也不会随机映射出来端口来
[root@Docker ~]# docker run -d -p 90:80 --name t02 test:2.0
[root@Docker ~]# docker ps
6d4aed83832f test:2.0 "nginx -g 'daemon of…" 2 seconds ago Up 1 second 0.0.0.0:90->80/tcp, [::]:90->80/tcp'
✅️ 虽然不可以通过-P(大写)随机映射,但我们可以-p(小写)指定端口映射
✅️ 毕竟容器里面,运行着Nginx服务
[root@Docker ~]# curl localhost:90
This is my page
2)暴漏端口
"多个端口空格隔开"
[root@Docker ~]# vim dockerfile-02
# 在Dockerfile中加入下面这句话
EXPOSE 80
[root@Docker ~]# docker build -f dockerfile-02 -t test:3.0 .
[root@Docker ~]# docker run -d --name t03 test:3.0
'先不加-P'
[root@Docker ~]# docker ps
IMAGE COMMAND PORTS NAMES
test:3.0 "nginx -g 'daemon of…" 80/tcp t03'
'没有-P,只是提示你,有个端口可以映射' --> 仅做声明
[root@Docker ~]# docker run -d -P --name t04 test:3.0
✅️ -P能够随机映射到宿主机端口,前提是你得暴漏端口
[root@Docker ~]# docker ps
3ede1c2b420b test:3.0 "nginx -g 'daemon of…" 2 seconds ago Up 1 second 0.0.0.0:32768->80/tcp, [::]:32768->80/tcp t04'
# 容器中的80端口映射到宿主机中的32768端口
[root@Docker ~]# curl localhost:32768
This is my page

ADD拷贝#

COPY 与 ADD 的选择

  • 最佳实践是优先使用 COPY
  • 只有当你需要自动解压本地压缩包(如 .tar.gz)时,才使用 ADD
  • 💚相同点:
    • 都可以拷贝文件 —> 从宿主机到容器
  • ❤️不同点:
    • COPY 可以用于多阶段构建 <— 后面笔记📚有
      • COPY 指令支持 --from=<stage> 这个参数来实现跨阶段复制
      • ADD无法实现 ❌️
    • ADD 指令支持解压tar包
      • 仅支持tar包, zip包不能解压

🔍 分层存储机制理解

使用COPY:分为两个层级(COPY tar包 → RUN解压)

  • 为了让镜像更加的轻量, 我们在 RUN 解压tar包 && 删除tar包

    • 放在一条命令中
  • ==实际上==:虽然最终联合视图中用户看不到tar包,但镜像大小仍包含这些内容

  • 本质:分层存储中,删除操作只是在当前层(RUN层)标记删除

    • 实际数据仍存在于镜像中(==COPY层中==)

🎯 Dockerfile构建优化

  • build构建镜像的时候, 是有缓存的
    • 存在于宿主机中

缓存机制

  • 从上到下逐层构建
  • 如果某一层的指令与之前构建的镜像层相同,则直接使用缓存
  • 一旦某一层没有命中缓存,其后的所有层都将重新构建

如何充分利用缓存呢❓️

  • 指令顺序优化
    • 基本不变不常改动的指令放在Dockerfile前面
    • 这些层最容易命中缓存
    • 后续变化频繁的层更新时,不会影响前面的缓存

🚀 ==镜像瘦身技巧==

  • 在同一RUN指令中完成安装和清理:
    • 基于 alpine 镜像
  • 合并在一个层,确保中间产物不会增加最终镜像大小
    • ⬇️ 缓存被真正清除
RUN apk update && \
apk add nginx && \
rm -rf /var/cache
  • ❌️==错误示范==
RUN apk update && apk add nginx
...其他指令...
RUN rm -rf /var/cache
  • 类比👆的(COPY tar包 → RUN 解压&&删除 tar包)

    • 最终并没有彻底删除掉
  • 即使后续指令删除了缓存文件,之前的层中仍然保留着这些文件

FROM alpine:3.20.2
LABEL maintainer="Jiuzhao"
WORKDIR /usr/
RUN apk update && \
apk add nginx && \
rm -rf /var/cache
RUN mkdir /code
COPY ./default.conf /etc/nginx/http.d/
RUN echo "This is my page" > /code/index.html
CMD ["nginx","-g","daemon off;"]
Terminal window
"清理环境"
[root@Docker ~]# docker rm -f $(docker ps -aq)
[root@Docker ~]# docker rmi test:1.0 test:2.0 test:3.0 server:1.0
# 把没有用的镜像也删除了
[root@Docker ~]# vim dockerfile-01
[root@Docker ~]# ls
default.conf dockerfile-01
[root@Docker ~]# docker build -f dockerfile-01 -t web:1.0 .
[root@Docker ~]# docker images
web:1.0 6894a72f7fc8 13.9MB

ENTRYPOINT启动命令#

RUN 与 CMD/ENTRYPOINT 的区别

  • RUN:是在构建镜像时执行的

    • 比如安装软件,构建完就结束
  • CMD/ENTRYPOINT:是在容器启动时执行的

    • 比如启动 Nginx 服务,是镜像运行后的==主进程==
    • 多个 CMD 指令,只有最后一个会被执行

==面试题==: ENTRYPOINT和CMD的区别

  • 相同点: 当用户没有指定容器的启动命令时,都可以作为容器的默认启动命令

  • 不同点:

    • CMD指定的启动命令

    • 容易被 docker run 后的参数覆盖

    • ENTRYPOINT

    • 该命令不会被覆盖,适合做固定入口

    • 与后面的CMD或者docker run命令==同时使用==时

      • 将作为==参数==传递给它
Terminal window
1)CMD指令演示
[root@Docker ~]# docker run -d --name v1 web:1.0
'正常启动'
# 默认CMD,启动Nginx服务
[root@Docker ~]# docker run -d --name v2 web:1.0 tail -f /etc/hosts
'给了运行命令'
--> ⚠️覆盖原来的 CMD nginx -g daemon off;
[root@Docker ~]# docker ps
IMAGE COMMAND NAMES
web:1.0 "tail -f /etc/hosts" v2
web:1.0 "nginx -g 'daemon of…" v1'
[root@Docker ~]# docker exec v1 ps -ef
PID USER TIME COMMAND
1 root 0:00 nginx: master process nginx -g daemon off;
7 nginx 0:00 nginx: worker process
8 nginx 0:00 nginx: worker process
9 root 0:00 ps -ef
[root@Docker ~]# docker exec v2 ps -ef
PID USER TIME COMMAND
1 root 0:00 tail -f /etc/hosts
7 root 0:00 ps -ef
✅️ v2的Nginx并没有启动
✅️ `CMD指令` 容易被 `docker run` 后的参数覆盖
❤️ 我们可以把 run 后面的参数也理解为一个 CMD指令
"多个CMD指令,只会执行最后一个,被覆盖了"
2)ENTRYPOINT指令演示
[root@Docker ~]# sed -i 's#CMD#ENTRYPOINT#' dockerfile-01
[root@Docker ~]# tail -1 dockerfile-01
ENTRYPOINT ["nginx","-g","daemon off;"]
[root@Docker ~]# docker build -f dockerfile-01 -t web:2.0 .
[root@Docker ~]# docker run -d --name c1 web:2.0
# 依旧是默认启动
[root@Docker ~]# docker run -d --name c2 web:2.0 tail -f /etc/hosts
# 继续以 tail -f /etc/hosts 这个命令启动
[root@Docker ~]# docker ps -a
IMAGE COMMAND STATUS NAMES
web:2.0 "nginx -g 'daemon of…" Exited (1) c2'
web:2.0 "nginx -g 'daemon of…" Up 2 minutes c1'
⚠️ c2退出了,而且是🔴异常退出的
# 从这个角度看 COMMAND命令,似乎一样!
[root@Docker ~]# docker ps -a --no-trunc
# 把它展开
"nginx -g 'daemon off;' tail -f /etc/hosts" Exited (1) c2
"nginx -g 'daemon off;'" Up 6 minutes c1
✅️ 后面紧跟着 tail -f /etc/hosts 参数
✅️ 后面的命令(CMD指令)将作为"参数"传给启动命令
✅️ ENTRYPOINT没法被覆盖,就算用户run时指定了命令,也只能作为参数传递
# 因为没有"nginx -g 'daemon off;' tail -f /etc/hosts"这条命令
# 容器异常退出 Exited (1)
3)错误Dockerfile
# 验证传参
[root@Docker ~]# vim dockerfile-01
[root@Docker ~]# tail -2 dockerfile-01
ENTRYPOINT ["nginx","-g","daemon off;"]
CMD ["tail","-f","/etc/hosts"]
[root@Docker ~]# docker build -f dockerfile-01 -t web:3.0 .
[root@Docker ~]# docker run -d --name c3 web:3.0
[root@Docker ~]# docker ps -a --no-trunc | grep c3
web:3.0 "nginx -g 'daemon off;' tail -f /etc/hosts" 46 seconds ago Exited (1) 45 seconds ago c3
# 运行后,直接退出了,Exited (1)
# 看命令,CMD被作为参数传递给ENTRYPOINT
4)✅️正确的使用
[root@Docker ~]# vim dockerfile-01
[root@Docker ~]# tail -2 dockerfile-01
ENTRYPOINT ["tail","-f"]
CMD ["/etc/hosts"]
[root@Docker ~]# docker build -f dockerfile-01 -t web:4.0 .
[root@Docker ~]# docker run -d --name c4 web:4.0
[root@Docker ~]# docker ps | grep c4
web:4.0 "tail -f /etc/hosts" Up 12 seconds c4
❤️ 看启动命令 ❤️
✅️ "当CMD和ENTRYPOINT一起使用时"
✅️ CMD将作为参数传递给ENTRYPOINT
[root@Docker ~]# docker run -d --name c5 web:4.0 /etc/hostname
[root@Docker ~]# docker ps | grep c5
web:4.0 "tail -f /etc/hostname" Up 7 seconds c5
✅️ /etc/hostname覆盖掉了CMD ["/etc/hosts"],现在是查看/etc/hostname
✅️ ENTRYPOINT ["tail","-f"]依旧没有改变,适合作为固定入口
❤️ 把docker run后面的命令,看做CMD指令,覆盖掉前面的CMD指令

VOLUME数据卷#

==数据卷== 创建一个挂载点,用于数据持久化,防止容器删除后数据丢失

在Dockerfile中使用VOLUME指令时

  • 它只能指定容器内的路径 ✅️
    • ==匿名挂载==
  • 而不能指定宿主机上的具体路径 ❌️
Terminal window
'环境清理'
[root@Docker ~]# docker rm -f $(docker ps -aq)
[root@Docker ~]# docker rmi web:1.0 web:2.0 web:3.0 web:4.0
[root@Docker ~]# docker volume prune -fa
# -a选项,即使是手动创建的,也可以删除掉
[root@Docker ~]# docker volume ls
DRIVER VOLUME NAME
# 没有任何数据卷
1)修改dockerfile文件
[root@Docker ~]# vim dockerfile-01
[root@Docker ~]# tail -2 dockerfile-01
VOLUME /code/
CMD ["nginx","-g","daemon off;"]
✅️ 这里只需要指定容器里面的路径即可
[root@Docker ~]# docker build -f dockerfile-01 -t web:1.0 .
[root@Docker ~]# docker volume ls
DRIVER VOLUME NAME
# 此时依旧没有数据卷被创建
[root@Docker ~]# docker run -d --name v1 web:1.0
'这里并没有指定数据卷!'
[root@Docker ~]# docker volume ls
DRIVER VOLUME NAME
local 1b6146e932...xxx
'有对应的匿名数据卷创建出来了!'
[root@Docker ~]# docker inspect v1 | grep -A 5 Mounts
"Mounts": [
{
"Type": "volume",
"Name": "1b6146e932e5d73992bedc544aa13298917b16677c28567b93b46149977e1d90",
"Source": "/var/lib/docker/volumes/1b6146e932e5d73992bedc544aa13298917b16677c28567b93b46149977e1d90/_data",
"Destination": "/code",
[root@Docker ~]# cd /var/lib/docker/volumes/1b6146e932e5d73992bedc544aa13298917b16677c28567b93b46149977e1d90/_data
[root@Docker _data]# ls
index.html
[root@Docker _data]# cat index.html
This is my page
[root@Docker _data]# echo Hello > index.html
'更改容器里面的默认主页'
[root@Docker _data]# docker exec v1 tail -1 /etc/hosts
172.17.0.2 a1030f07fc29
[root@Docker _data]# curl 172.17.0.2
Hello

ENV环境变量#

  • 使用键值对的形式

    • ENV =

    • 比如 ENV MYPATH=/app

    • 容器运行时也能读到 -> $MYPATH <- Dockerfile也可引用

  • 等价于 docker run -e 传递环境变量

Terminal window
[root@Docker ~]# vim dockerfile-01
[root@Docker ~]# tail -4 dockerfile-01
ENV school=oldboyedu \
name=Jiuzhao
RUN echo "$name Welcome To You"
CMD ["nginx","-g","daemon off;"]
[root@Docker ~]# docker build -f dockerfile-01 -t web:2.0 .
=> [7/7] RUN echo "Jiuzhao Welcome To You"
✅️ 在Dockerfile中,ENV变量也可以被解析和使用
[root@Docker ~]# docker run -d --name v2 web:2.0
[root@Docker ~]# docker exec v2 env
PATH=...xxx/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=9b982519ccd6
school=oldboyedu
name=Jiuzhao
# 这些环境变量都可以读取到

HEALTHCHECK健康检查#

  • 定义命令来检测容器服务是否存活
    • curl 多一点

💡 depends_on(docker compose) 默认只等容器启动,不等服务就绪,加 healthcheck 可以解决启动顺序问题

Terminal window
HEALTHCHECK [OPTIONS] CMD command
'当然你也可以使用别的command命令进行健康监测💚'
HEALTHCHECK --interval=10s --timeout=10s --retries=3 \
CMD curl -f http://localhost/ || exit 1
--interval # 执行健康检查的间隔时间(默认: 30s)
--timeout # 单次健康检查的超时时间(默认: 30s)
🌰 一个检查指令过去,30s都没有回应,则认为此次检查失败
✅️ "一般和上面间隔时间保持一致"
--retries # 连续失败多少次后将容器标记为 unhealthy
🌰 间隔 n 秒进行检查
=================================
"start_period" 参数不是 Docker 健康检查的必需参数
📌 "预热期" --> 给它"预热时间"
start_period:
1.定义容器启动后的初始等待时间
2.在这段时间内,健康检查不会被视为失败 ✅️
3.默认值为 0
以下情况建议保留:
🌰 你的 MySQL 容器需要较长的启动时间(比如需要加载大量数据)
🌰 你想避免在 MySQL 初始化期间出现健康检查失败的误报
curl -f
# 如果访问成功(返回 200-399 状态码)
# curl 退出码为 0,健康检查通过
# 如果访问失败(返回 400-599 状态码)
# curl 退出码为非零值,触发 || exit 1,健康检查失败
"这里访问的是容器本地,所以容器得启动Nginx"
# 如果Nginx没有起来,curl失败,标记为不健康
Terminal window
[root@Docker ~]# vim dockerfile-01
[root@Docker ~]# tail -3 dockerfile-01
HEALTHCHECK --interval=10s --timeout=10s --retries=3 \
CMD curl -f http://localhost/ || exit 1
CMD ["nginx","-g","daemon off;"]
[root@Docker ~]# docker build -f dockerfile-01 -t web:3.0 .
[root@Docker ~]# docker ps | grep v3
"nginx -g 'daemon of…" Up(health: starting) v3'
# 现在的状态是启动中ing
🦄等了半分钟后....
"nginx -g 'daemon of…" Up(unhealthy) v3'
❌️ 变为了不健康了! ❌️
[root@Docker ~]# docker exec v3 ps -ef
PID USER TIME COMMAND
1 root 0:00 nginx: master process nginx -g daemon off;
7 nginx 0:00 nginx: worker process
8 nginx 0:00 nginx: worker process
102 root 0:00 ps -ef
'可是明明启动着 --> Nginx'
[root@Docker ~]# docker exec v3 tail -1 /etc/hosts
172.17.0.4 9484756b05e1
[root@Docker ~]# curl 172.17.0.4
This is my page
# 也可拉取页面呀!
===============================
所以问题到底出现在哪里呢❓️
⚠️一定要"在容器中"手动执行命令试试,观察👀问题到底出现在哪!
[root@Docker ~]# docker exec v3 curl http://localhost
"curl": executable file not found in $PATH
# 用curl确实是没有拉取到页面!
'❌️容器里面没有curl命令!'
[root@Docker ~]# docker exec v3 apk add curl
OK: 15 MiB in 26 packages
# 下载完成之后,再次查看状态
[root@Docker ~]# docker ps
"nginx -g 'daemon of…" Up(healthy) v3'
✅️ 现在这个状态就对了
===============================
Dockerfile文件优化
[root@Docker ~]# vim dockerfile-01
[root@Docker ~]# tail -4 dockerfile-01
RUN apk add curl
HEALTHCHECK --interval=10s --timeout=10s --retries=3 \
CMD curl -f http://localhost/ || exit 1
CMD ["nginx","-g","daemon off;"]
[root@Docker ~]# docker build -f dockerfile-01 -t web:4.0 .
[root@Docker ~]# docker run -d --name v4 web:4.0
[root@Docker ~]# docker ps | grep v4
"nginx -g 'daemon of…" Up(health: starting) v4'
🦄 最后也是变为健康❤️了
"nginx -g 'daemon of…" Up(healthy) v4'

服务案例#

Terminal window
"环境清理"
[root@Docker ~]# docker rm -f $(docker ps -aq)
[root@Docker ~]# docker rmi web:1.0 web:2.0 web:3.0 web:4.0
🦄查看最新创建容器的IP地址
"{{.NetworkSettings.Networks.bridge.IPAddress}}"
[root@Docker ~]# docker run -d -it --name v1 alpine:3.20.2
[root@Docker ~]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} v1
172.17.0.2
# 但是这里是指定容器 v1
'把v1换成最新创建的容器即可'
[root@Docker ~]# docker ps -l
# 展示最新latest创建的容器
alpine:3.20.2 "/bin/sh" Up 5 minutes v1
[root@Docker ~]# docker ps -lq
-q # 只显示容器id
0884c7b3fb9d
[root@Docker ~]# docker ps -n=1 -q
0884c7b3fb9d
-n=1 # 也是展示最新创建的容器
[root@Docker ~]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} $(docker ps -n=1 -q)
172.17.0.2
'综合一下 --> 可以写到Xshell的快捷键中'

ssh自定义密码#

Terminal window
"基础镜像Alpine"
制作镜像,要求支持ssh服务,具体要求如下:
✅️ 支持启动容器时修改密码,若不指定则默认密码为:"123456"
1)安装&&启动ssh服务
[root@Docker ~]# docker run -d -it --name t1 alpine:3.20.2
[root@Docker ~]# docker exec -it t1 sh
/ # sshd
sh: sshd: not found
'这里是ssh让别人可以登录,所以需要下载server服务端'
/ # apk add openssh-server
OK: 9 MiB in 17 packages
/ # sshd
sshd re-exec requires execution with an absolute path
# 需要绝对路径
/ # which sshd
/usr/sbin/sshd
/ # /usr/sbin/sshd
sshd: no hostkeys available -- exiting.
'缺少有效的主机密钥对'
✅️ 需要的是主机密钥,而不是用户密钥对
❌️ 用户密钥对: ssh-keygen -t ed25519 -f ~/.ssh/my_key -N ''
✅️ 主机秘钥对: ssh-keygen -A
/ # /usr/sbin/sshd
'以绝对路径启动'
/ # ps -ef
PID USER COMMAND
23 root sshd: /usr/sbin/sshd [listener] 0 of 10-100 startups
# 出现这个进程就是sshd运行成功了
2)远程测试
[root@Docker ~]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} $(docker ps -n=1 -q)
172.17.0.3
# 查看它的IP地址
[root@Docker ~]# ssh root@172.17.0.3
The host '172.17.0.3 (172.17.0.3)' can't be established.'
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
root@172.17.0.3's password: '
Permission denied, please try again.
⚠️ 这里忘记给root秘密了
# 进入到容器中
/ # passwd root
Changing password for root
New password: "1"
Bad password: too short
Retype password: "1"
passwd: password for root changed by root
3)再次测试
[root@Docker ~]# ssh root@172.17.0.3
root@172.17.0.3's password: '
Permission denied, please try again.
"明明我输入的就是1,但是还是拒绝连接了!"❓️
✅️ 默认是拒绝root远程连接的
/ # grep PermitRootLogin /etc/ssh/sshd_config
#PermitRootLogin prohibit-password
"把#去掉,并yes允许root远程登录"
#后向引用
/ # sed -ri 's@#(PermitRootLogin).*@\1 yes@' /etc/ssh/sshd_config
-r # 支持扩展正则
s@@@@g # 因为这里有#
\1 # 代表的就是括起来的(),把想要的取出来即可
/ # grep PermitRootLogin /etc/ssh/sshd_config
PermitRootLogin yes
4)再次测试
[root@Docker ~]# ssh root@172.17.0.3
root@172.17.0.3's password: '
Permission denied, please try again.
# 好像还是不行啊
"重启sshd服务试试"
/ # killall sshd
/ # /usr/sbin/sshd
'杀死后,在启动'
[root@Docker ~]# ssh root@172.17.0.3
root@172.17.0.3's password: '
Welcome to Alpine!
b63803a3e7ed:~# tail -1 /etc/hosts
172.17.0.3 b63803a3e7ed
✅️ 成功登录上去了
Terminal window
1)创建对应的目录
[root@Docker ~]# mkdir /lb-web-ssh
[root@Docker ~]# cd /lb-web-ssh/
[root@Docker lb-web-ssh]#
[root@Docker lb-web-ssh]# mkdir ./source
# 存放配置文件
[root@Docker lb-web-ssh]# mkdir ./dockerfile
# Dockerfile文件
[root@Docker lb-web-ssh]# tree /lb-web-ssh/
/lb-web-ssh/
├── dockerfile
└── source
2)v0.1版
root@Docker lb-web-ssh]# vim ./source/default.conf
server {
listen 80 default_server;
server_name _;
root /code;
location / {
index index.html;
}
}
[root@Docker lb-web-ssh]# vim ./dockerfile/v0.1
'同时安装curl为后面健康检查❤️做准备'
# 同时下载sshd --> 允许别人连接
FROM alpine:3.20.2
LABEL maintainer="Jiuzhao"
EXPOSE 80 22
RUN apk update && \
apk add nginx curl openssh-server && \
rm -rf /var/cache
RUN mkdir /code && \
ssh-keygen -A && \
sed -ri 's@#(PermitRootLogin).*@\1 yes@' /etc/ssh/sshd_config
COPY ./default.conf /etc/nginx/http.d/
RUN echo "This is my page" > /code/index.html
CMD ["nginx","-g","daemon off;"]
Terminal window
'默认启动Nginx'
[root@Docker lb-web-ssh]# docker build -f ./dockerfile/v0.1 -t web:0.1 ./source/
[+] Building 43.9s (10/10) FINISHED
[root@Docker lb-web-ssh]# docker images web:0.1
web:0.1 97993117c2a2 24.3MB
[root@Docker lb-web-ssh]# docker rm -f $(docker ps -aq)
[root@Docker lb-web-ssh]# docker run -d --name v1 web:0.1
[root@Docker lb-web-ssh]# docker exec -it v1 sh
/ # passwd root
'先去给root密码'
Changing password for root
New password:
Bad password: too short
Retype password:
passwd: password for root changed by root
/ # ps -ef
PID USER TIME COMMAND
1 root 0:00 nginx: master process nginx -g daemon off;
7 nginx 0:00 nginx: worker process
8 nginx 0:00 nginx: worker process
9 root 0:00 sh
16 root 0:00 ps -ef
/ # /usr/sbin/sshd
'启动sshd服务'
/ # ps -ef
PID USER TIME COMMAND
1 root 0:00 nginx: master process nginx -g daemon off;
7 nginx 0:00 nginx: worker process
8 nginx 0:00 nginx: worker process
9 root 0:00 sh
19 root 0:00 sshd: /usr/sbin/sshd [listener] 0 of 10-100 startups
20 root 0:00 ps -ef
/ # exit
[root@Docker lb-web-ssh]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} $(docker ps -n=1 -q)
172.17.0.2
[root@Docker lb-web-ssh]# ssh root@172.17.0.2
root@172.17.0.2's password: '
Welcome to Alpine!
4bb94c86838a:~# tail -1 /etc/hosts
172.17.0.2 4bb94c86838a
==================================
缺点:
1.需要手动启动sshd服务
2.需要手动给root密码

v1.0优化#

Terminal window
1)多服务启动
# 写一个脚本来把它们同时启动起来
'Alpine镜像中没有bash只有sh'
/ # /bin/bash
❌️ sh: /bin/bash: not found
/ # /bin/sh
/ # ✅️ exit
/ #
[root@Docker lb-web-ssh]# vim ./source/start.sh
'后面需要把这个脚本也拷贝过去'
#!/bin/sh
# start sshd
/usr/sbin/sshd
# start nginx
nginx -g "daemon off;"
==================================
COPY ./start.sh /code/
CMD ["/bin/sh","/code/start.sh"]
2)非交互式地设置密码
/ # chpasswd --help
BusyBox v1.36.1 (2024-06-10 07:11:47 UTC)
'Alpine默认是有的,所以就不用下载了'
/ # echo "root:123456" | chpasswd
chpasswd: password for 'root' changed
[root@Docker ~]# ssh root@172.17.0.2
root@172.17.0.2's password:' --> 123456
Welcome to Alpine!
4bb94c86838a:~# 密码成功被修改✅️
==================================
# 设置一个变量,并修改root密码
ENV ROOT_PASSWD=123456
RUN echo "root:${ROOT_PASSWD}" | chpasswd
3)Dockerfile编写
✅️ 加上健康检查
[root@Docker lb-web-ssh]# vim ./dockerfile/v1.0
'重新整合'
FROM alpine:3.20.2
LABEL maintainer="Jiuzhao"
ENV ROOT_WD=123456
EXPOSE 80 22
RUN apk update && \
apk add nginx curl openssh-server && \
rm -rf /var/cache
RUN mkdir /code && \
ssh-keygen -A && \
echo "root:${ROOT_WD}" | chpasswd && \
echo "This is my page" > /code/index.html && \
sed -ri 's@#(PermitRootLogin).*@\1 yes@' /etc/ssh/sshd_config
COPY ./default.conf /etc/nginx/http.d/
COPY ./start.sh /code/
HEALTHCHECK --interval=10s --timeout=10s --retries=3 \
CMD curl -f http://localhost/ || exit 1
CMD ["/bin/sh","/code/start.sh"]
Warning

ENV 中的key值不要太敏感 —> PASSWD

  • 不然后面build构建镜像时,会有警告⚠️
  • Do not use ARG or ENV instructions(介绍) for sensitive(敏感的) data
    • ENV “ROOT_PASSWD”
    • 后面改为ROOT_WD就不警告了✅️

COPY 指令本身永远无法执行多个命令,因为:

  • COPY 是 Docker 的配置指令,不是 shell 命令
    • ==&& \==❌️ 不可行
    • 两个COPY指令实现
Terminal window
[root@Docker lb-web-ssh]# tree /lb-web-ssh/
/lb-web-ssh/
├── dockerfile
│   ├── v0.1
│   └── v1.0
└── source
├── default.conf
└── start.sh
[root@Docker lb-web-ssh]# docker build -f ./dockerfile/v1.0 -t web:1.0 ./source/
[root@Docker lb-web-ssh]# docker images web:1.0
web:1.0 df6624c37956 24.3MB
[root@Docker lb-web-ssh]# docker run -d --name v2 web:1.0
[root@Docker lb-web-ssh]# docker ps
"/bin/sh /code/start…" Up(health: starting) 22/tcp,80/tcp v2
# 再次查看 health: starting --> healthy
[root@Docker lb-web-ssh]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} $(docker ps -n=1 -q)
172.17.0.3
[root@Docker lb-web-ssh]# curl 172.17.0.3
This is my page
[root@Docker lb-web-ssh]# ssh root@172.17.0.3
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
'这个登录不上去是因为,同一个IP,我们以前有ssh登录过,记录的有对应的指纹'
# 但是这个IP的指纹和之前登陆过的指纹不一致,所以不让你登录
✅️ 防止中间人攻击
[root@Docker lb-web-ssh]# > /root/.ssh/known_hosts
# 把里面的指纹清理一下
[root@Docker lb-web-ssh]# ssh root@172.17.0.3
root@172.17.0.3's password': --> 123456
Welcome to Alpine!
7550665f3771:~# tail -1 /etc/hosts
172.17.0.3 7550665f3771
✅️ 这次使用的是默认密码 123456
'我们也可以 docker run -e ROOT_WD=指定密码'
[root@Docker lb-web-ssh]# docker run -d -e ROOT_WD=passwd --name test web:1.0
[root@Docker lb-web-ssh]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} $(docker ps -n=1 -q)
172.17.0.4
[root@Docker lb-web-ssh]# ssh root@172.17.0.4
root@172.17.0.4's password': --> passwd
Permission denied, please try again.
root@172.17.0.4's password': --> passwd
Permission denied, please try again.
root@172.17.0.4's password': --> 123456
Welcome to Alpine!
9367d6e642d1:~# exit
Connection to 172.17.0.4 closed.
'密码没有改过来!'
为什么呢❓️
# 在构建镜像的时候
RUN echo "root:${ROOT_WD}" | chpasswd
# 已经把它写死了
'这个命令应该放在启动脚本中start.sh'
# 👆 容器的启动命令
# 当有新的变量出现时,重新给root赋值
[root@Docker lb-web-ssh]# echo 'echo "root:${ROOT_WD}" | chpasswd' >> ./source/start.sh
'追加到脚本末尾'
[root@Docker lb-web-ssh]# tail -1 ./source/start.sh
echo "root:${ROOT_WD}" | chpasswd
[root@Docker lb-web-ssh]# sed -i '/root:${ROOT_WD}/d' ./dockerfile/v1.0
✅️ 并在Dockerfile中删除它并加入到启动脚本
[root@Docker lb-web-ssh]# docker build -f ./dockerfile/v1.0 -t web:1.1 ./source/
# 这次是1.1版本
[root@Docker lb-web-ssh]# docker run -d --name hh -e ROOT_WD=oldboy web:1.1
[root@Docker lb-web-ssh]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} $(docker ps -n=1 -q)
172.17.0.5
[root@Docker lb-web-ssh]# ssh root@172.17.0.5
root@172.17.0.5's password': --> oldboy
Permission denied, please try again.
root@172.17.0.5's password': --> 123456
Permission denied, please try again.
❌️ 两个密码都不对!!!
[root@Docker lb-web-ssh]# docker inspect hh
# 看看环境变量传过去了没有❓️
"Env": [
"ROOT_WD=oldboy",
# 变量也对呀!
✅️ 实际上 echo "root:${ROOT_WD}" | chpasswd 压根没有执行
[root@Docker lb-web-ssh]# tail -3 ./source/start.sh
# start nginx
nginx -g "daemon off;"
echo "root:${ROOT_WD}" | chpasswd
✅️ nginx放在前台运行了,会被阻塞住
# --> 后面的命令没有办法执行
'调整一下位置就可以!'
[root@Docker lb-web-ssh]# tail -3 ./source/start.sh
echo "root:${ROOT_WD}" | chpasswd
# start nginx
nginx -g "daemon off;"
[root@Docker lb-web-ssh]# docker build -f ./dockerfile/v1.0 -t web:1.2 ./source/
⚠️ 这次是1.2版本
[root@Docker lb-web-ssh]# docker run -d -e ROOT_WD=oldboy --name xx web:1.2
[root@Docker lb-web-ssh]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} $(docker ps -n=1 -q)
172.17.0.6
[root@Docker lb-web-ssh]# ssh root@172.17.0.6
root@172.17.0.6's password: '
Welcome to Alpine!
63dd4cdc2145:~# tail -1 /etc/hosts
172.17.0.6 63dd4cdc2145
✅️ '终于是可以了'
[root@Docker lb-web-ssh]# docker run -d --name fs web:1.2
# 不传参,默认就是123456
[root@Docker lb-web-ssh]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} $(docker ps -n=1 -q)
172.17.0.7
[root@Docker lb-web-ssh]# ssh root@172.17.0.7
'有点问题! --> 我们是在Dockerfile中写的 ssh-keygen -A '
# 主机秘钥被写死了!
# 后面基于它的启动的容器,主机指纹是一致的
ED25519 key fingerprint is SHA256:BKCNN5J6r8iW2iu0gAs5eQzdW6wGBM7xtOcxQtNJves.
This host key is known by the following other names/addresses:
~/.ssh/known_hosts:5: 172.17.0.5 🌰
~/.ssh/known_hosts:8: 172.17.0.6 🌰
root@172.17.0.7's password: '
Welcome to Alpine!
ca0b892545c9:~# exit
✅️ 删除Dockerfile中的命令,把它放在启动脚本中
# 这样每次启动的容器,主机指纹也是不一样的!
[root@Docker lb-web-ssh]# sed -i '/ssh-keygen/d' ./dockerfile/v1.0
[root@Docker lb-web-ssh]# vim ./source/start.sh
'生成主机密钥的指令需要在启动sshd服务之前才行!!'
❌️ 否则sshd没有办法启动起来
#!/bin/bash
# start sshd
ssh-keygen -A
/usr/sbin/sshd
echo "root:${ROOT_WD}" | chpasswd
# start nginx
nginx -g "daemon off;"
[root@Docker lb-web-ssh]# docker build -f ./dockerfile/v1.0 -t web:1.3 ./source/
⚠️ 这次是1.3版本
[root@Docker lb-web-ssh]# docker run -d --name c1 web:1.3
[root@Docker lb-web-ssh]# docker run -d --name c2 -e ROOT_WD=123 web:1.3
[root@Docker lb-web-ssh]# docker run -d --name c3 -e ROOT_WD=passwd web:1.3
'IP地址分别是 172.17.0.8 172.17.0.9 172.17.0.10 '
[root@Docker lb-web-ssh]# ssh root@172.17.0.8
ED25519 key fingerprint is SHA256:I+hlnxgcM2siRncwqna996MSvsKQZbH4DUMlIJXLlH0.
root@172.17.0.8's password: '
Welcome to Alpine!
[root@Docker lb-web-ssh]# ssh root@172.17.0.9
ED25519 key fingerprint is SHA256:AGRRebloBJFZ5/RSZS8HP/lEG1tSua5jS5MUXCDnld0.
root@172.17.0.9's password: '
Welcome to Alpine!
[root@Docker lb-web-ssh]# ssh root@172.17.0.10
ED25519 key fingerprint is SHA256:X7w8YbxJrQ7u5jEPy/FobQyzg9u35ZnYZqlt3G7yaoA.
root@172.17.0.10's password: '
Welcome to Alpine!
✅️ 现在每次的主机密钥都不相同了 ✅️
[root@Docker lb-web-ssh]# docker run -d --name cc web:1.2
❌️ 这次是1.2版本 ❌️
[root@Docker lb-web-ssh]# ssh root@172.17.0.11
ED25519 key fingerprint is SHA256:BKCNN5J6r8iW2iu0gAs5eQzdW6wGBM7xtOcxQtNJves.
This host key is known by the following other names/addresses:
~/.ssh/known_hosts:5: 172.17.0.5
~/.ssh/known_hosts:8: 172.17.0.6
~/.ssh/known_hosts:9: 172.17.0.7
'他们的主机秘钥都是一样的!'

v2.0优化#

server {
listen 80 default_server;
server_name _;
root /code;
location / {
index index.html;
}
}
  • vim ./source/default.conf

#!/bin/bash
# start sshd
ssh-keygen -A
/usr/sbin/sshd
echo "root:${ROOT_WD}" | chpasswd
# start nginx
nginx -g "daemon off;"
  • vim ./source/start.sh

FROM alpine:3.20.2
LABEL maintainer="Jiuzhao"
ENV ROOT_WD=123456
EXPOSE 80 22
RUN apk update && \
apk add nginx curl openssh-server && \
rm -rf /var/cache
RUN mkdir /code && \
echo "This is my page" > /code/index.html && \
sed -ri 's@#(PermitRootLogin).*@\1 yes@' /etc/ssh/sshd_config
COPY ./default.conf /etc/nginx/http.d/
COPY ./start.sh /code/
HEALTHCHECK --interval=10s --timeout=10s --retries=3 \
CMD curl -f http://localhost/ || exit 1
CMD ["/bin/sh","/code/start.sh"]
  • vim ./dockerfile/v1.0
Terminal window
"环境清理"
[root@Docker lb-web-ssh]# docker rm -f $(docker ps -aq)
[root@Docker lb-web-ssh]# docker rmi web:0.1 web:1.0 web:1.1 web:1.2 web:1.3
[root@Docker lb-web-ssh]# rm -rf ./dockerfile/v0.1
[root@Docker lb-web-ssh]# tree /lb-web-ssh/
/lb-web-ssh/
├── dockerfile
│   └── v1.0
└── source
├── default.conf
└── start.sh
1)构建镜像
[root@Docker lb-web-ssh]# docker build -f ./dockerfile/v1.0 -t web:1.0 ./source/
[root@Docker lb-web-ssh]# docker images
web:1.0 7eb7a083ccc2 24.3MB
Tip
  • Dockerfile中, 最后指令是CMD, 容易被docker run后的命令覆盖
Terminal window
[root@Docker lb-web-ssh]# docker run -d --name v1 web:1.0 passwd
"后面传了一个参数 passwd "
[root@Docker lb-web-ssh]# docker ps -a
web:1.0 "passwd" Exited (1) v1
❌️ 直接异常退出了
# 覆盖了CMD指令
=================================
如何把后面的参数作为密码传给环境变量呢❓️
1.在Dockerfile中把CMD更换为ENTRYPOINT
# CMD会被覆盖,ENTRYPOINT可以传参
# 因为我们运行的是shell脚本,后面是可以跟参数的
2.在shell脚本中,使用if-else判断
if [ -n "$1" ];then
# 如果传参数了($1非空),则直接用这个参数作为root密码
# docker run xxx 参数(密码)
echo "root:${1}" | chpasswd
else [ -n "$ROOT_WD" ]
# 没有参数($1为空)但ROOT_WD变量存在且非空
# 则用ROOT_WD变量作为root密码
# Dockerfile中 ROOT_WD=123456 (默认值)
# docker run -e ROOT_WD=新密码 xxx
echo "root:${ROOT_WD}" | chpasswd
fi
-n ✅️ 用于测试字符串长度是否大于0(非空)
✅️ 后面的变量用⚠️双引号⚠️包围
# 规避空格或其他特殊字符
# 不加引号 --> if判断有问题❌️
=================================
[root@Docker lb-web-ssh]# vim ./source/start.sh
#!/bin/bash
# start sshd
ssh-keygen -A
/usr/sbin/sshd
if [ -n "$1" ];then
echo "root:${1}" | chpasswd
else [ -n "$ROOT_WD" ]
echo "root:${ROOT_WD}" | chpasswd
fi
# start nginx
nginx -g "daemon off;"
[root@Docker lb-web-ssh]# vim ./dockerfile/v2.0
[root@Docker lb-web-ssh]# tail ./dockerfile/v2.0
# 只是更改了一下,最后默认的启动方式 CMD --> ENTRYPOINT
ENTRYPOINT ["/bin/sh","/code/start.sh"]
[root@Docker lb-web-ssh]# docker build -f ./dockerfile/v2.0 -t web:2.0 ./source/
[root@Docker lb-web-ssh]# docker images
web:2.0 aa06dc8c5f1a 24.3MB
[root@Docker lb-web-ssh]# docker rm -f $(docker ps -aq)
[root@Docker lb-web-ssh]# docker run -d --name v1 web:2.0
[root@Docker lb-web-ssh]# docker ps
web:2.0 "/bin/sh /code/start…" Up(healthy) 22/tcp,80/tcp v1
[root@Docker lb-web-ssh]# docker exec v1 ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/sh /code/start.sh
9 root 0:00 sshd: /usr/sbin/sshd [listener]...xx
12 root 0:00 nginx: master process nginx -g daemon off;
13 nginx 0:00 nginx: worker process
14 nginx 0:00 nginx: worker process
'进程也都起来了!'
[root@Docker lb-web-ssh]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} $(docker ps -n=1 -q)
172.17.0.2
1)默认密码: 123456
"docker run -d --name v1 web:2.0"
[root@Docker ~]# ssh root@172.17.0.2
root@172.17.0.2's password': --> 123456
Welcome to Alpine!
959400652de9:~# exit
2)手动 -e 指定密码
[root@Docker ~]# docker run -d -e ROOT_WD=passwd --name v2 web:2.0
[root@Docker ~]# ssh root@172.17.0.3
root@172.17.0.3's password': --> passwd
Welcome to Alpine!
7a27760b2666:~# exit
3)run运行后面传参
[root@Docker ~]# docker run -d --name v3 web:2.0 oldboyedu
[root@Docker ~]# ssh root@172.17.0.4
root@172.17.0.4's password': --> oldboyedu
Welcome to Alpine!
580cae44499f:~# exit

❤️排错流程#

Terminal window
[root@Docker lb-web-ssh]# docker ps -a
web:2.0 "/bin/sh /code/start…" Exited (2) v1
❌️ 异常退出!
[root@Docker lb-web-ssh]# tail -2 ./dockerfile/v2.0
# ENTRYPOINT ["/bin/sh","/code/start.sh"]
ENTRYPOINT ["tail","-f","/etc/hosts"]
✅️ 把上面的先注释掉,先保证容器运行起来
1)重新构建镜像
[root@Docker lb-web-ssh]# docker build -f ./dockerfile/v2.0 -t web:2.0 ./source/
2)正常启动起来
[root@Docker lb-web-ssh]# docker run -d --name v2 web:2.0
[root@Docker lb-web-ssh]# docker ps
web:2.0 "tail -f /etc/hosts" Up 1 second v1
3)进入到容器中测试
[root@Docker lb-web-ssh]# docker exec -it v2 sh
/ # ls /code/
index.html start.sh
/ # /bin/sh /code/start.sh
/code/start.sh: line 8: syntax error:
✅️ 这里面会显示详细的错误位置📍
'在容器中调试脚本 ---> 更容易排错'

LB负载调度#

  • 👆ssh服务完结 —> ==完善LB负载==
    • 自由调度比例 —> shell脚本取参数
      • sed awk 替换
Terminal window
具体要求如下:
✅️ 支持nginx调度比例变量(传的参数不同,比例不同)
# 默认3:1
[root@Docker ~]# docker run -d --name v4 -e weight=5:3 web:2.0
[root@Docker ~]# docker exec v4 env
PATH=/usr/sbin:/usr/bin:/sbin:/bin
...
weight=5:3
ROOT_WD=123456
HOME=/root
# 这里面有我们传递的参数
1)如何取值
✅️ cut && awk
[root@Docker ~]# docker exec -it v4 sh
/ # echo ${weight}
5:3
# echo $weight | cut -d ":" -f 1
5
/ # echo $weight | cut -d ":" -f 2
3
'上面是cut命令,当然类似的功能awk也是可以实现的'
/ # echo $weight | awk -F ":" '{print $1}'
5
/ # echo $weight | awk -F ":" '{print $2}'
3
2)准备配置文件
/ # vi lb.conf
upstream webs {
server web01 weight=3;
server web02 weight=1;
}
server {
listen 80;
server_name diy.kpyun.com;
location / {
proxy_pass http://webs;
proxy_set_header Host $http_host;
proxy_http_version 1.1;
}
}
/ # Left=$(echo $weight | cut -d ":" -f 1)
/ # Right=$(echo $weight | awk -F ":" '{print $2}')
'先分别赋值给不同的变量'
/ # echo $Left
5
/ # echo $Right
3
'再sed替换'
/ # sed -ri "s#(weight=)3#\1${Left}#g" lb.conf
/ # sed -ri "s#(weight=)1#\1${Right}#g" lb.conf
/ # head -3 lb.conf
upstream webs {
server web01 weight=5;
server web02 weight=3;
3)Dockerfile编写
[root@Docker ~]# cd /lb-web-ssh/
[root@Docker lb-web-ssh]# vim ./source/lb.conf
'把上面的配置文件拷贝过去!'
⚠️ 配置文件中默认权重为3:1 --> 已经写到配置文件中去了
[root@Docker lb-web-ssh]# vim ./dockerfile/v3.0
✅️ 这次拷贝lb.conf
✅️ ENV声明多个变量 "空格隔开" --> weight=3:1
ENV key1=value1 key2=value2 key3=value3
FROM alpine:3.20.2
LABEL maintainer="Jiuzhao"
ENV ROOT_WD=123456 weight=3:1
EXPOSE 80 22
RUN apk update && \
apk add nginx curl openssh-server && \
rm -rf /var/cache
RUN mkdir /code && \
echo "This is my page" > /code/index.html && \
sed -ri 's@#(PermitRootLogin).*@\1 yes@' /etc/ssh/sshd_config
COPY ./lb.conf /etc/nginx/http.d/
COPY ./start.sh /code/
HEALTHCHECK --interval=10s --timeout=10s --retries=3 \
CMD curl -f http://localhost/ || exit 1
ENTRYPOINT ["/bin/sh","/code/start.sh"]
Terminal window
4)脚本整合
"docker run -e weight=5:3 xxx"
# 启动容器的同时📌传递环境变量,可以替换配置文件中的权重
[root@Docker lb-web-ssh]# vim ./source/start.sh
#!/bin/bash
# start sshd
ssh-keygen -A
/usr/sbin/sshd
if [ -n "$1" ];then
echo "root:${1}" | chpasswd
else [ -n "$ROOT_WD" ]
echo "root:${ROOT_WD}" | chpasswd
fi
# start nginx
Left=$(echo $weight | cut -d ":" -f 1)
Right=$(echo $weight | awk -F ":" '{print $2}')
sed -ri "s#(weight=)3#\1${Left}#g" /etc/nginx/http.d/lb.conf
sed -ri "s#(weight=)1#\1${Right}#g" /etc/nginx/http.d/lb.conf
nginx -g "daemon off;"
5)web服务器准备&&自定义页面
[root@Docker lb-web-ssh]# docker rm -f $(docker ps -aq)
'先把web01 和web02 创建过出来'
👆用的v2.0版本的镜像
'因为配置文件中 server web01; '
✅️ 得用自定义网络
[root@Docker lb-web-ssh]# docker network ls
5297e0639824 mynet bridge local
# 如果没有自行创建网络
[root@Docker lb-web-ssh]# docker run -d --name web01 --net mynet web:2.0
[root@Docker lb-web-ssh]# docker run -d --name web02 --net mynet web:2.0
[root@Docker lb-web-ssh]# docker ps
web:2.0 "/bin/sh /code/start…" Up 22/tcp, 80/tcp web02
web:2.0 "/bin/sh /code/start…" Up 22/tcp, 80/tcp web01
[root@Docker lb-web-ssh]# docker exec -it web01 sh
/ # echo "web01..." > /code/index.html
/ # curl localhost
web01...
/ # exit
[root@Docker lb-web-ssh]# docker exec -it web02 sh
/ # echo "web02..." > /code/index.html
/ # curl localhost
web02...
/ # exit
6)生成镜像
"这次是v3.0"
[root@Docker lb-web-ssh]# docker build -f ./dockerfile/v3.0 -t web:3.0 ./source/
[root@Docker lb-web-ssh]# docker images
web:3.0 ecf6b5894872 24.3MB
[root@Docker lb-web-ssh]# docker run -d --name lb --net mynet web:3.0
[root@Docker lb-web-ssh]# docker ps -n=1
web:3.0 "/bin/sh /code/start…" Up 22/tcp, 80/tcp lb
[root@Docker lb-web-ssh]# docker exec lb ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/sh /code/start.sh
10 root 0:00 sshd: /usr/sbin/sshdxxx....
21 root 0:00 nginx: master process nginx -g daemon off;
22 nginx 0:00 nginx: worker process
23 nginx 0:00 nginx: worker process
'进程也都起来了'
[root@Docker lb-web-ssh]# docker inspect -f {{.NetworkSettings.Networks.mynet.IPAddress}} $(docker ps -n=1 -q)
'中间这个换为mynet而不是bridge'
172.20.0.4
[root@Docker lb-web-ssh]# tail -1 /etc/hosts
172.20.0.4 diy.kpyun.com
[root@Docker lb-web-ssh]# for i in `seq 10`;do curl diy.kpyun.com;done
web01... ❤️
web01... ❤️
web01... ❤️
web02... 💚
web01... ❤️
web01... ❤️
web01... ❤️
web02... 💚
web01... ❤️
web01... ❤️
'也就是3:1的样子'
7) -e 指定环境变量
[root@Docker lb-web-ssh]# docker run -d --name lb-test -e weight=5:1 --net mynet web:3.0
⚠️ -e weight=5:1 ⚠️
[root@Docker lb-web-ssh]# docker inspect -f {{.NetworkSettings.Networks.mynet.IPAddress}} $(docker ps -n=1 -q)
172.20.0.5
[root@Docker lb-web-ssh]# sed -i 's#172.20.0.4#172.20.0.5#g' /etc/hosts
[root@Docker lb-web-ssh]# tail -1 /etc/hosts
172.20.0.5 diy.kpyun.com
[root@Docker lb-web-ssh]# for i in `seq 10`;do curl diy.kpyun.com;done
web01... ❤️
web01... ❤️
web01... ❤️
web01... ❤️
web02... 💚
web01... ❤️
web01... ❤️
web01... ❤️
web01... ❤️
web01... ❤️
[root@Docker lb-web-ssh]# curl diy.kpyun.com
web02... 💚
'每5次web01 --> 就该web02了'

httpd服务编写#

  • Apache 服务
    • 也是==web服务==的一种
    • 没有Nginx处理==高并发==的能力
Terminal window
"清理环境"
[root@Docker ~]# docker rm -f $(docker ps -aq)
[root@Docker ~]# vim /tmp/httpd.img
FROM alpine:3.20.2
LABEL maintainer="Shihao"
EXPOSE 80
WORKDIR /root/
RUN apk update && \
apk add apache2 curl && \
rm -rf /var/cache && \
echo 'It works! Apache httpd' > /var/www/localhost/htdocs/index.html
VOLUME /var/www/
HEALTHCHECK --interval=10s --timeout=10s --retries=3 \
CMD curl -f http://localhost/ || exit 1
ENTRYPOINT ["httpd","-D","FOREGROUND","-e","info"]
Terminal window
[root@Docker ~]# docker build -f /tmp/httpd.img -t myhttp:1.0 $(pwd)
[+] Building 0.3s (7/7) FINISHED
...........
=> => unpacking to docker.io/library/myhttp:1.0
[root@Docker ~]# docker image ls myhttp
myhttp:1.0 4424dd2544b9 25.7MB
[root@Docker ~]# docker run -d --name v1 -P myhttp:1.0
[root@Docker ~]# docker inspect -f {{.NetworkSettings.Networks.bridge.IPAddress}} $(docker ps -n=1 -q)
172.17.0.2
[root@Docker ~]# docker inspect v1 | grep -i -A 2 'Hostip' | head -2
"HostIp": "0.0.0.0",
"HostPort": "32768"
[root@Docker ~]# docker container ls -l
fb20cb437808 myhttp:1.0 "httpd -D FOREGROUND…" 4 minutes ago Up 4 minutes (healthy) 0.0.0.0:32768->80/tcp, [::]:32768->80/tcp v1
[root@Docker ~]# curl 172.17.0.2
It works! Apache httpd
[root@Docker ~]# curl localhost:32768
It works! Apache httpd

扩展指令#

ARG变量#

Terminal window
面试题: ARG和ENV的区别❓️
💚相同点:
都可以向容器传递环境变量
❤️不同点:
1.ARG传递环境变量的生命周期⚠️"仅在构建镜像阶段有效"
✅️ 仅在Dockerfile中生效
✅️ 在构建镜像时, 可被替换 `--build-arg key=value`
2.ENV传递环境变量的生命周期在⚠️"镜像构建阶段和容器运行阶段均有效"
✅️ 容器运行后,也生效 --> env命令可以查看到
✅️ 在运行容器时, 可以被替换 `-e key=value`
1)环境清理
[root@Docker lb-web-ssh]# cd
[root@Docker ~]# rm -rf /lb-web-ssh/
[root@Docker ~]# docker rm -f $(docker ps -aq)
[root@Docker ~]# docker volume prune -fa
[root@Docker ~]# docker rmi web:1.0 web:2.0 web:3.0
2)Dockerfile文件编写
# 这次不玩花活🎆了
# 文件全部在/root目录下创建了
[root@Docker ~]# pwd
/root
[root@Docker ~]# rm -rf ./*
[root@Docker ~]# vim Dockerfile
FROM alpine:3.20.2
LABEL maintainer="Jiuzhao"
ARG school=kpyun
ENV user=Shi
RUN mkdir /redhat && \
touch /redhat/$school && \
touch /redhat/$user
CMD ["/bin/sh"]
Terminal window
3)构建镜像
[root@Docker ~]# docker build -f Dockerfile -t demo:1.0 .
[root@Docker ~]# docker build -f Dockerfile -t demo:2.0 --build-arg school=heima .
--build-arg ✅️ 替换Dockerfile中的ARG变量
"kpyun" ---> "heima"
[root@Docker ~]# docker images | grep demo
demo:1.0 d614a574b7d1 11.7MB
1)保留ARG变量
[root@Docker ~]# docker run -d -it --name v1 demo:1.0
[root@Docker ~]# docker exec v1 env
PATH=/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=d32238bbaa48
user=Shi
HOME=/root
'只有user=Shi,并没有ARG中的school=kpyun'
[root@Docker ~]# docker exec v1 ls /redhat
Shi
kpyun --> 仅在构建镜像时 $school生效
2)替换ARG变量
[root@Docker ~]# docker run -d -it --name v2 demo:2.0
[root@Docker ~]# docker exec v2 env
PATH=/usr/sbin:/usr/bin:/sbin:/bin
...xxx
user=Shi
'自然是没有ARG中的变量' --> 只在Dockerfile中生效
[root@Docker ~]# docker exec v2 ls /redhat
Shi
heima --> 把原有的 kpyun 替换掉了
3)替换ENV变量
[root@Docker ~]# docker run -d -it --name v3 -e user=Jiu demo:2.0
⚠️ -e user=Jiu
[root@Docker ~]# docker exec v3 env
PATH=/usr/sbin:/usr/bin:/sbin:/bin
...xxx
user=Jiu
[root@Docker ~]# docker exec v3 ls /redhat
Shi <--- 为什么这里没有被替换❓️
heima
✅️ 这两个文件是在构建镜像的时候就被创建好了的

USER运行用户#

==指定容器运行用户==或 UID

  • 容器中必须有对应用户或UID
  • 有的服务不能使用root启动
Terminal window
"清理环境"
[root@Docker ~]# docker rm -f $(docker ps -aq)
1)正常启动
✅️ 默认root用户运行
[root@Docker ~]# docker run -d -it --name v1 alpine:3.20.2
[root@Docker ~]# docker exec v1 whoami
root
[root@Docker ~]# docker exec v1 cat /etc/passwd
root❌0:0:root:/root:/bin/sh
....xxx
sync❌5:0🔄/sbin:/bin/sync
nobody❌65534:65534:nobody:/:/sbin/nologin
'要指定的用户必须在/etc/passwd中存在'
2)Dockerfile编写
[root@Docker ~]# > Dockerfile
[root@Docker ~]# vim Dockerfile
FROM alpine:3.20.2
USER oldboy
CMD ["/bin/sh"]
[root@Docker ~]# docker build -f Dockerfile -t demo:3.0 .
'成功构建'
# 运行试试
[root@Docker ~]# docker run -d -it --name v2 demo:3.0
docker: Error response xxx unable to find user oldboy:
"不能找到oldboy用户,在passwd文件中"
no matching entries in passwd file
3)已存在的用户
[root@Docker ~]# sed -i 's#oldboy#nobody#g' Dockerfile
'更换为nobody'
[root@Docker ~]# docker build -f Dockerfile -t demo:3.0 .
'覆盖构建'
[root@Docker ~]# docker run -d -it --name v3 demo:3.0
[root@Docker ~]# docker exec v3 whoami
⚠️nobody⚠️ --> 而不是root
[root@Docker ~]# docker exec v3 ps -ef
PID USER TIME COMMAND
1 nobody 0:00 /bin/sh
14 nobody 0:00 ps -ef
4)创建&&并指定用户
[root@Docker ~]# vim Dockerfile
FROM alpine:3.20.2
RUN adduser -D oldboy && \
echo "oldboy:passwd" | chpasswd
USER oldboy
CMD ["/bin/sh"]
=====================================
-D # 免交互 --> 无密码账户 --> 否则会卡住
✅️ 从root切换至oldboy用户
📌 echo xxx | chpasswd "可省略"
5)再次构建&&测试
[root@Docker ~]# docker build -f Dockerfile -t demo:4.0 .
[root@Docker ~]# docker run -d -it --name v4 demo:4.0
[root@Docker ~]# docker exec v4 whoami
oldboy
[root@Docker ~]# docker exec v4 ps -ef | awk '{print $2}'
USER
oldboy
oldboy

ONBUILD基础镜像触发器#

🏗️ 什么是 ONBUILD❓️

  • ONBUILD 是 Docker 提供的一种**“延时执行”**机制

    • 🎯 定义时不执行,只有被继承时才执行
  • 你可以把它想象成一个”当别人继承我时,就做这些事”的指令

Terminal window
1)清理环境
[root@Docker ~]# rm -rf ./*
[root@Docker ~]# docker rm -f $(docker ps -aq)
[root@Docker ~]# docker images
[root@Docker ~]# docker rmi demo:1.0 demo:2.0 demo:3.0 demo:4.0
2)创建爷爷👴镜像
[root@Docker ~]# vim yeye
FROM alpine:3.20.2
LABEL maintainer=Jiu
ONBUILD ENV school=kpyun
ONBUILD RUN touch /test.md && \
echo This From yeye >> /test.md
CMD ["/bin/sh"]
[root@Docker ~]# docker run -d -it --name v1 demo:1.0
[root@Docker ~]# docker exec -it v1 sh
/ # env
PATH=/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=b8d62ec454b6
HOME=/root
'没有school变量 && 也没有test.md文件'
✅️ "定义时不执行,只有被继承时才执行"
/ # ls / | grep test.md
/ # exit
[root@Docker ~]# docker image inspect demo:1.0 -f {{.Config.Labels}}
map[maintainer:Jiu]
✅️ 只有它的标签🏷️ && OnBuild字段
[root@Docker ~]# docker image inspect demo:1.0 -f {{.Config.OnBuild}}
[ENV school=kpyun
RUN touch /test.md && echo This From yeye >> /test.md]
3)创建父镜像
[root@Docker ~]# vim fu
FROM demo:1.0
RUN touch /fu.md
ONBUILD ENV my_user=heima
CMD ["/bin/sh"]
[root@Docker ~]# docker build -f fu -t demo:2.0 .
[root@Docker ~]# docker run -d -it --name v2 demo:2.0
[root@Docker ~]# docker exec -it v2 sh
/ # env
PATH=/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=909df5bfc1eb
school=kpyun
'这些信息继承于yeye👴'
/ # ls / | grep test.md
test.md
/ # cat /test.md
This From yeye --> yeye👴
/ # ls /fu.md
/fu.md <-- 本身创建的
4)创建孙子镜像
[root@Docker ~]# vim sunzi
FROM demo:2.0
CMD ["/bin/sh"]
[root@Docker ~]# docker build -f sunzi -t demo:3.0 .
[root@Docker ~]# docker run -d -it --name v3 demo:3.0
[root@Docker ~]# docker exec -it v3 sh
/ # env
PATH=/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=22e7483f41be
school=kpyun <-- ❗️ 继承于父镜像
my_user=heima --> ✅️ ONBUILD 在发力
=============================================
[root@Docker ~]# docker image inspect demo:2.0 -f {{.Config.OnBuild}}
[ENV my_user=heima]
=============================================
/ # ls / | grep test.md
test.md <-- ❗️ 继承于父镜像
/ # cat test.md
This From yeye <-- ❗️ 继承于父镜像
❗️"因为父镜像本身有这些东西,所以被继承过来了"
👇 和它来源一样!
/ # ls /fu.md
/fu.md

📌自定义基础镜像#

scratch 是 Docker 官方提供的一个特殊镜像

  • 它是一个==空的镜像==,什么都没有,也被称为”基础镜像”

  • 当你写 FROM scratch 时,这意味着你从零开始构建镜像


清华镜像站

  • 👆==链接==

image-20260429195619870
image-20260429195619870

  • 找根文件系统 —> 下载

image-20260429195704415
image-20260429195704415

Terminal window
1)上传并解压
[root@Docker ~]# rm -rf ./*
[root@Docker ~]# rz -E
rz waiting to receive.
[root@Docker ~]# ls
rootfs.tar.xz
[root@Docker ~]# mkdir test
[root@Docker ~]# ls
rootfs.tar.xz test
[root@Docker ~]# tar xf rootfs.tar.xz -C ./test/
[root@Docker ~]# ls ./test/
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
2)更改根目录信息
[root@Docker ~]# pwd
/root
[root@Docker ~]# ls
rootfs.tar.xz test
[root@Docker ~]# cd ./test/
[root@Docker test]# cat ./etc/os-release
✅️ 我们可以在里面进行随意的更改!!
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.23.4
PRETTY_NAME="Jiuzhao996"
HOME_URL="https://kpyun.fun/"
[root@Docker test]# tar zcf Jiuzhao-rootfs.tar.xz *
'将当前的所有文件都打包!'
[root@Docker test]# ls Jiuzhao-rootfs.tar.xz
Jiuzhao-rootfs.tar.xz
✅️ 这个就成我们自己的根文件系统了
3)返回上一级目录
[root@Docker test]# cp Jiuzhao-rootfs.tar.xz ../
[root@Docker test]# cd ../
[root@Docker ~]# ls
Jiuzhao-rootfs.tar.xz rootfs.tar.xz test
[root@Docker ~]# rm -rf rootfs.tar.xz test/
[root@Docker ~]# ls
Jiuzhao-rootfs.tar.xz
✅️ 只留下这个压缩包即可
4)编写自己的基础镜像
[root@Docker ~]# vim scratch.dockerfile
FROM scratch
LABEL maintainer=Jiuzhao
ADD Jiuzhao-rootfs.tar.xz /
CMD ["/bin/sh"]
[root@Docker ~]# docker build -f scratch.dockerfile -t mylinux:1.0 $(pwd)
[root@Docker ~]# docker image ls mylinux
mylinux:1.0 7657409cad41 15.5MB
5)测试与验证
[root@Docker ~]# docker history mylinux:1.0
CREATED CREATED BY
49 seconds ago CMD ["/bin/sh"]
49 seconds ago ADD Jiuzhao-rootfs.tar.xz /
49 seconds ago LABEL maintainer=Jiuzhao
[root@Docker ~]# docker run -d -it --name m1 mylinux:1.0
root@Docker ~]# docker ps
mylinux:1.0 "/bin/sh" Up 1 second m1
[root@Docker ~]# docker exec m1 cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.23.4
PRETTY_NAME="Jiuzhao996"
HOME_URL="https://kpyun.fun/"
[root@Docker ~]# docker exec m1 ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/sh
14 root 0:00 ps -ef

Dockerfile优化#

.dockerignore文件#

.dockerignore 文件用于指定在构建 Docker 镜像时需要忽略的文件和目录

  • 它告诉 Docker 哪些文件不需要发送到==服务端(Docker daemon)==
    • Docker 也是经典的 ==C/S架构==

✅️ 主要优势:提升构建速度 —> 减少需要传输的文件数量

多阶段构建#

  • 在一个 Dockerfile 中定义多个 FROM 指令

  • 每个 FROM 开启一个新的构建阶段

  • 分离构建环境运行环境,只保留必要文件

  • COPY 指令支持 --from=<stage> 这个参数来实现跨阶段复制

    • ADD无法实现 ❌️

nginx: download

  • 👆去下载稳定版的源码包
Terminal window
1)准备nginx的源码包
[root@Docker ~]# rm -rf ./*
[root@Docker ~]# wget https://nginx.org/download/nginx-1.30.0.tar.gz
'下载nginx源码包'
# 注意版本号
2)编写Dockerfile文件
[root@Docker ~]# vim multiple-step.dockerfile
# --- 第一阶段:构建环境 ---
FROM ubuntu:22.04 AS base
# AS base 起的别名
# 后面 --from=base 引用它
# 更新包列表并安装编译 Nginx 所需的依赖
RUN apt-get update && apt-get install -y \
gcc \
make \
libpcre3-dev \
zlib1g-dev \
openssl \
libssl-dev \
wget && \
rm -rf /var/lib/apt/lists/* && \
rm -rf /var/cache
# 拷贝 Nginx 源码包
ADD nginx-1.30.0.tar.gz /tmp/nginx-build
# 进入源码目录,配置、编译、安装
WORKDIR /tmp/nginx-build/nginx-1.30.0
RUN ./configure \
--prefix=/soft \
--sbin-path=/soft/sbin/nginx \
--conf-path=/soft/conf/nginx.conf \
--error-log-path=/soft/logs/error.log \
--http-log-path=/soft/logs/access.log \
--pid-path=/soft/run/nginx.pid \
--lock-path=/soft/run/nginx.lock \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-pcre \
&& make && make install
# --- 第二阶段:运行环境 ---
FROM ubuntu:22.04
# 只安装 Nginx 运行时所需的依赖和库文件
RUN apt-get update && apt-get install -y \
libpcre3 \
libssl3 && \
rm -rf /var/lib/apt/lists/* && \
rm -rf /var/cache
# 把第一阶段编译安装好的 Nginx 目录复制过来
COPY --from=base /soft /soft
# 两者目录结构必须保持一致,相同的位置
# ADD,不能 --from 用于多阶段构建
# 定义启动命令
CMD ["/bin/bash"]
Terminal window
FROM ubuntu:22.04 AS base
✅️ 第一阶段基础镜像起的别名👆
COPY --from=base /soft /soft
--form # 引用第一阶段的镜像 别名
# 两者目录结构必须保持一致,相同的位置
# 不然后面会报错的,一直找不到文件
'刚开始我是 /code --> /usr/local/nginx/'
# 编译的是/code,现在把它移到/usr/local/nginx/
❌️ 尝试启动的时候,依旧会去编译时的目录去找文件
root@04356adb932f:/# ls /usr/local/nginx/
conf html logs run sbin
root@04356adb932f:/# /usr/local/nginx/sbin/nginx
nginx: [alert] could not open error log file: open() "/code/logs/error.log" failed (2: No such file or directory)
2026/04/30 01:37:20 [emerg] 18#0: open() "/code/conf/nginx.conf" failed (2: No such file or directory)
✅️ 当前容器压根就没有这个目录 "/code"
Terminal window
3)构建&&测试验证
[root@Docker ~]# docker build -t demo:1.0 -f multiple-step.dockerfile .
[root@Docker ~]# docker run -d -it --name myweb demo:1.0
[root@Docker ~]# docker exec -it myweb /bin/bash
root@9dad99e49cbe:/# ls /soft/
conf html logs run sbin
root@9dad99e49cbe:/# /soft/sbin/nginx
'成功启动'
root@9dad99e49cbe:/# cd /soft/
root@9dad99e49cbe:/# echo Hello World > ./html/index.html
'修改默认主页'
[root@Docker ~]# curl 172.17.0.2
Hello World
# 在宿主机中成功拉去页面

第一阶段 (AS base)

👆创建==构建环境==

  • ubuntu:22.04 为基础镜像启动构建
  • 安装了大量编译工具(如 gcc, make)和开发库(如 libpcre3-dev),
    • 这些在最终运行时是不需要的
  • 下载 Nginx 源码,进行配置、编译 (make) 并安装 (make install)
    • /soft 目录

第二阶段

👆创建==运行环境==

  • 再次以 ubuntu:22.04 为基础镜像启动新的构建
  • 只安装 Nginx 运行时真正需要的库和依赖
  • 关键一步:COPY --from=base /soft /soft
  • 将第一阶段的 /soft 目录
    • 包含了编译好的 Nginx 二进制文件和相关文件
    • 完整地复制到了当前运行时镜像的相同路径下
  • 清理不必要的构建依赖和缓存
  • 最终生成的镜像只包含 ==运行Nginx== 所需的最小文件集合

  • 不包含任何编译工具、源码或临时文件,大大减小了镜像体积

  • 分离构建环境运行环境,只保留必要文件

优化总结#

1️⃣ ==构建速度优化==

  • 将不常变化的指令放在 Dockerfile 上方,提高缓存命中率
  • 使用国内或私有软件源,提升软件包下载速度
  • 将大型软件包==提前下载==到本地,通过 COPY/ADD 直接复制
  • 使用 .dockerignore 排除不必要的文件,减少传输到 Docker daemon 的数据量

2️⃣ ==镜像体积优化==

  • 清理缓存和垃圾:及时删除构建过程中的临时文件和无用软件包
  • 选择轻量基础镜像:使用 Alpine 或其他精简版基础镜像
  • 合并多条Dockefile指令:减少镜像层数,优化存储效率
  • 采用多阶段构建:分离构建环境运行环境,只保留必要文件
Caution

构建过程中避免使用==交互命令==,否则会导致构建意外终止

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Dockerfile详解
https://www.kpyun.fun/posts/docker/docker05/
作者
久棹
发布于
2026-02-24
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
久棹
只要胆子大,天天寒暑假!
公告
欢迎来到久棹的技术小站!本站专注 Linux 运维学习笔记分享,如有问题欢迎交流探讨 🎉
分类
标签
站点统计
文章
98
分类
11
标签
203
总字数
244,453
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.13.5
文章许可
CC BY-NC-SA 4.0

文章目录