RUN
docker file 中的 RUN 命令相对来教容易理解
- RUN 指令用于在构建镜像时执行命令,这些命令会在 Docker 镜像的构建过程中执行。常用于安装软件包、设置环境变量、创建目录等。
- RUN 指令会在镜像构建中创建新的镜像层,每个 RUN 指令都会创建一个新的镜像层。
- RUN 指令执行的命令会在构建阶段生效,而不会在容器运行时执行。
关键是
RUN 可以有多条
RUN 只会在构建阶段执行, 在容器启动时不会执行
提供1个例子:
我们准备1个docker file
test_cmd1
FROM busybox
RUN echo "hello run"
RUN echo "hello run2"
CMD ["echo", "hi"]
当build 时, RUN两条命令都被执行了, 但是CMD 的没有执行
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$docker build -f test_cmd1 -t test_cmd1:latest .
create mode 100644 dockerfile/test_cmd1
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
Install the buildx component to build images with BuildKit:
https://docs.docker.com/go/buildx/
Sending build context to Docker daemon 8.192kB
Step 1/4 : FROM busybox
---> 65ad0d468eb1
Step 2/4 : RUN echo "hello run"
---> Running in 276d14886a99
hello run
Removing intermediate container 276d14886a99
---> 1323139f939a
Step 3/4 : RUN echo "hello run2"
---> Running in 483674db3603
hello run2
Removing intermediate container 483674db3603
---> dbdc9963c5dc
Step 4/4 : CMD ["echo", "hi"]
---> Running in 3db1c9c199d9
Removing intermediate container 3db1c9c199d9
---> 9ff4d1d0ea33
Successfully built 9ff4d1d0ea33
Successfully tagged test_cmd1:latest
但是启动容器时, 只有CMD 那句被执行
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd1:latest
hi
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$
RUN 命令顺便带过了, 不是本文的重点
CMD
- CMD 指令用于指定容器启动时要运行的默认命令。它可以被 Dockerfile 中的 ENTRYPOINT 指令覆盖。
- 如果 Dockerfile 中有多个 CMD 指令,只有最后一个 CMD 指令会生效。
- CMD 指令的内容会在容器启动时执行,可以通过 docker run 命令传递参数来覆盖默认命令。
我们先讲下CMD的写法
原则上, CMD 后面的内容应该是1个数组
CMD [“executeable”, “arg1”, “arg2”…]
其中第1个参数必须是1个在容器内可以执行的命令(定义在$PATH 中的 可执行文件)
但是CMD 有多个写法
我们用各种例子来体现
例子1
# ok
# command "echo hhih"
CMD ["echo", "hhih"]
这种写法是可以的
正常输出:
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd3:latest
hhih
可以用docker ps -a --no-trunc 命令来查看容器被启动时真正执行的命令
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker ps -a --no-trunc | head -n 2
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7f4be762c74eaaa281010eab2b40a1cd39a638ff688e63d371d0166cfdd23a21 test_cmd3:latest "echo hhih" 18 minutes ago Exited (0) 18 minutes ago upbeat_kirch
可见执行的命令就是 “echo hhih"
CMD [“echo”, “hhih”] 这种写法就是符合了第1个参数是可以执行命令, 第2个参数是args 的原则.
例子2
# error unable to start container process: exec: "echo hhih", and pts hundled in host server
# command "echo hhih"
CMD ["echo hhih"]
这种写法会启动时会出错
因为CMD 第1个参数是 “echo hhih" 并不是1个可执行文件命令
例子3
# ok
# command "/bin/sh -c 'echo hhih'"
CMD echo hhih
这种写法是可以的, 实际上 后面的字符串按照空格被自动分割成多个item, 第1个item是echo , 第2个是hhih
但是这种写法与例子1还是有点区别, 就是当容器被启动时, 执行的命令是
/bin/sh -c ‘echo hhih’
例子4
# first CMD will be ignored
CMD echo hhih
CMD echo hhihb
这种写法break了CMD 只能有单个的原则, 但是构建镜像时不会出错, 只是容器启动时只会执行最后一条CMD 命令, 而忽略之前的
例子5
# error /bin/sh: echo hhih: not found
# command "/bin/sh -c '\"echo hhih\"'"
CMD "echo hhih"
这种写法也是错误示范, 这里的echo hhih 被双引号扩着, 则会被CMD 认为是1个 元素item, 违反了第一个item 必须是可执行文件命令的原则
例子6
# ok
# command "/bin/sh -c '\"echo\" \"hhih\"'"
CMD "echo" "hhih"
这种写法是ok的类似 例子3
小结
CMD 的写法大概是两种
一种是 用中扩号括住, 显式数组写法
CMD [“executeable”, “args1”, “args2”…]
这种写法容器启动时 正常执行 executeable args1 args2 …
另1种写法是 不用中扩号
直接 CMD executeable args1 args2 …
这种写法容器启动时, 会被按照 sh -c " executeable args1 args2 …"
注意, 其实这两种写法还有变种, 例如显式利用sh -c
CMD [“sh”, “-c”, “echo hhic”]
CMD [“sh”, “-c”, “echo hhic, $0”, "hhic2]
CMD sh
-c
-|
echo hhic
等等, 但是都是基于上面提到的两大类扩展的, 这种写法也适合于 Dockerfile, cloudbuild yaml, k8s yaml中命令参数的编写
CMD 定义的命令可以在容器启动时被覆盖
这个特性很重要
例子:
定义1个 docker file
FROM busybox
CMD echo hhih
构建镜像
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ git pull && docker build -f test_cmd3 -t test_cmd3:latest .
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 8 (delta 6), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (8/8), 659 bytes | 131.00 KiB/s, done.
From e.coding.net:nvd11/bq/sql_backup
483f2de..438d518 master -> origin/master
Updating 483f2de..438d518
Fast-forward
dockerfile/test_cmd3 | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
Install the buildx component to build images with BuildKit:
https://docs.docker.com/go/buildx/
Sending build context to Docker daemon 8.704kB
Step 1/2 : FROM busybox
---> 65ad0d468eb1
Step 2/2 : CMD echo hhih
---> Using cache
---> 4c73e1a69ab2
Successfully built 4c73e1a69ab2
Successfully tagged test_cmd3:latest
正常执行时会输出
hhih
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd3:latest
hhih
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$
但是我们可以在docker run 命令后面加上命令覆盖掉CMD 定义的命令
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd3:latest echo hello
hello
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$
这个特性相当重要, 可以让我们的容器部署更加灵活
ENTRYPOINT
- ENTRYPOINT 指令用于配置容器启动时要运行的命令,它定义了容器的主要执行命令。
- 如果 Dockerfile 中有多个 ENTRYPOINT 指令,只有最后一个 ENTRYPOINT 指令会生效。
- ENTRYPOINT 指令的内容不会被覆盖,但可以通过 docker run 命令的 --entrypoint 选项来覆盖。
ENTRYPOINT 的用法是CMD 基本一样, 大部分场景下能互相替换
例如:
test_cmd4
FROM busybox
# error /bin/sh: echo hello: not found
# command /bin/sh -c '\"echo hello\"'
# ENTRYPOINT "echo hello"
# error /bin/sh: echo 'hello': not found
# command "/bin/sh -c '\"echo 'hello'\"'"
# ENTRYPOINT "echo 'hello'"
# error runc create failed: unable to start container process: exec: "echo hello"
# command "echo hello"
# ENTRYPOINT ["echo hello"]
# ok
# command "echo hello"
# ENTRYPOINT ["echo", "hello"]
# ok
# command "/bin/sh -c 'echo hello'"
# could not be append or override
# ENTRYPOINT echo hello
# ok
# command "/bin/sh -c 'echo hello;'"
# could not be append or override
ENTRYPOINT echo hello;
那么既然CMD 能用, 为何需要ENTRYPOINT
是因为 ENTRYPOINT 定义的命令在容器启动时一定会执行, 无法被覆盖
在某些场景下, 为了安全等原因, 不想容器启动命令被覆盖, 则应该考虑用ENTRYPOINT 来构建镜像
但是, ENTRYPOINT 仍然可以配合CMD 命令来令到命令的某些部分 or 参数可以被覆盖
例如下面3中写法都是ok的
# ok
# command "/bin/sh -c 'echo hello; $0'"
# $0 could be override
ENTRYPOINT echo hello; $0
# ok
# command "sh -c 'echo hello; $0'"
# $0 could be override
# equal to "ENTRYPOINT echo hello; $0"
ENTRYPOINT [ "sh", "-c", "echo hello; $0"]
# ok
# command "/bin/sh -c 'sh -c \"echo 'hello'; $0\"'"
# $0 could be override
ENTRYPOINT sh -c "echo 'hello'; $0"
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd4:latest
hello
gateman@DESKTOP-UIU9RFJ:~/projects/sql_backup/dockerfile$ docker run test_cmd4:latest "echo hi"
hello
hi
注意这里两点
- docker run 提供的参数必须用“括住”, 除非你在ENTRYPOINT 定义了两个参数 $0 和 $1
- 即使部分被覆盖, 但是 ENTRYPOINT 定义的部分仍然会执行