我们日常写Dockerfile的时候,如果想设置环境变量可以通过ENV指令,格式:
ENV <key1>=<value1> <key2>=<value2>...
但很少有童鞋注意到,为啥我们不能通过常规的RUN export 直接设置环境变量呢,为啥还需要引入一个ENV的指令,难道不是多此一举吗?很多看似简单的东西,如果不深入原理,很容易停留在现象的表层。
答案当然不是,通过RUN的方式在构建镜像的过程中,每次RUN都会生成一个中间层,这个中间层是RUN命令执行的上下文。但如果通过RUN export一个环境变量,在这RUN进程的上下文中是可以生效的,但伴随着这一层RUN命令的结束,这个环境变量的周期也随之结束,并没有持久化到镜像里面。下面通过两个案例说明一下:
案例一
我们先在终端一中启动一个容器,并设置一个TEST环境变量
# docker run -it busybox sh
# export TEST=test
登录到终端二中,进入容器查看环境变量
# docker exec -it 9c18a594aa09(终端一创建容器ID) sh
# env
输出的结果中并没有终端一里面设置的环境变量TEST。这个实验在本质上与Docker并无关系,而是Linux系统本来就是这样设计的,通过export设置的环境变量,只是在当前程序的上下文有效,当通过exec进入容器后,启动一个新进程,所以无法读取这个环境变量。
案例二
创建一个测试的Dockerfile文件,分别使用ENV和RUN export的方式设置环境变量。
FROM busybox
ENV FOO=foo
RUN export BAR=bar
RUN export BAZ=baz && echo “$FOO $BAR $BAZ“
执行构建命令docker build后将会输出下面的结果
# docker build -t testenv .
Sending build context to Docker daemon 2.048 kB
Step 1/4 : FROM busybox
---> d8233ab899d4
Step 2/4 : ENV FOO foo
---> Running in 51ddaa260ea7
---> f8a1656d3e7e
Removing intermediate container 51ddaa260ea7
Step 3/4 : RUN export BAR=bar
---> Running in 803d486a476a
---> 5f12d131af2a
Removing intermediate container 803d486a476a
Step 4/4 : RUN export BAZ=baz && echo “$FOO $BAR $BAZ“
---> Running in a9a6dd63eb90
foo baz
---> 0ad9d79ebc6c
Removing intermediate container a9a6dd63eb90
Successfully built 0ad9d79ebc6c
从输出的结果可以看到通过export设置的环境变量只能在当前进程的上下文有效,而无法持久化到镜像中。而通过ENV设置的环境变量将保存到镜像文件的config.json的文件中,从而可以持续有效。