活用 ENV 與 ARG

ENV 與 ARG 是 Dockerfile 的指令,它們能定義變數並且在後面的流程中使用。

ENV 的設計

ENV 比較容易理解,它其實就是設定 environment,因此概念上,它會是一個全域變數--直到 docker run 的時候都還會存在的變數。

Docker 官方的底層 image,如 PHPJava 等,會版本資訊、安裝路徑等設定成 ENV,在後續的流程可以拿來使用。

至於是應用層級的 image 如 Laravel image,environment 大多都會是執行時期才提供,而 Dockerfile 則可以設定預設值。至於要怎麼設定,則是看 image 最終是否有要拿到線上部署,如果有的話,建議預設 production 設定會比較好:

ENV APP_ENV=production
ENV APP_DEBUG=false

而連線設定則建議不要有預設值,否則部署錯環境加上設定也錯,若網路層沒有防呆的話,將會發生錯寫資料的悲劇。

ARG 的設計

ARG 是一個很像 ENV 的指令,不一樣的點主要在於,它只能活在 build image 的過程裡。可以從下面這個例子看得出來:

FROM alpine

ENV foo=1
ARG bar=2

# Build day27 時 echo
RUN echo ${foo} , ${bar}

# Run day27 時 echo
CMD echo ${foo} , ${bar}

build image 過程可以看到 ENV 與 ARG 有正常取值,但 docker run 的時候,則只剩下 ENV 而 ARG 不見了。這代表 ARG 只能活在 build image 階段而已。

ARG 不只是 build image 階段的變數,它可以在下指令的時候一併設值:

docker build --build-arg bar=3 .

ARG 的使用情境在,有時候需要寫很多 Dockerfile,如:

FROM php:7.3-alpine

RUN apk add --no-cache unzip
COPY --from=composer:1.10 /usr/bin/composer /usr/bin/composer
FROM php:7.4-alpine

RUN apk add --no-cache unzip
COPY --from=composer:1.10 /usr/bin/composer /usr/bin/composer

兩個 Dockerfile 差異只在於 PHP 版本,若以這個寫法來看,若未來新增 PHP 版本,就得多一個 Dockerfile。

這個情境就很適合使用 ARG 改寫:

ARG PHP_VER
FROM php:${PHP_VER}-alpine

RUN apk add --no-cache unzip
COPY --from=composer:1.10 /usr/bin/composer /usr/bin/composer
docker build --build-arg PHP_VER=7.4 .
docker build --build-arg PHP_VER=7.3 .

這裡的 ARG 寫法是沒有預設值的,這時 ${PHP_VER} 會取到的是空值。以這個例子,FROM 指令會出現 image 名稱格式錯誤訊息(php:-alpine):

$ docker build .
Sending build context to Docker daemon 111.6kB
Step 1/4 : ARG PHP_VER
Step 2/4 : FROM php:${PHP_VER}-alpine
invalid reference format

ARG 與 ENV 混用

如果在 ARG 設值的時候使用 ENV 的值,或是反過來的話,可以正常 work 的:

FROM alpine

ENV foo=is_env
ARG bar=is_arg

ARG foo_env=${foo}
ENV bar_arg=${bar}

RUN echo ${foo_env} , ${bar_arg}
CMD echo ${foo_env} , ${bar_arg}

可以看到 ARG 有正常取得 ENV,ENV 也有正常取得 ARG。

另一種情境是撞名:

FROM alpine

ENV foo=is_env
ARG foo=is_arg

ARG bar=is_arg
ENV bar=is_env

RUN echo ${foo} , ${bar}
CMD echo ${foo} , ${bar}

若取一樣的名字會發現,它最後都會以 ENV 為主。雖然不會出錯,但建議還是盡可能不要撞名比較好。

與 Multi-stage build 合併使用

下面做一個簡單的實驗,在第一個 stage 設定好值後,在第二個 stage 使用:

FROM alpine

ENV foo=1
ARG bar=2

FROM alpine

RUN echo ${foo} , ${bar}
CMD echo ${foo} , ${bar}

這個實驗非常簡單,一做就馬上理解:每個 stage 要用的 ARG 與 ENV 都需要各自定義的,因此兩個 stage 可以設定兩個同名不同值的 ENV;若兩個 stage 都使用同名的 ARG,則兩個 ARG 在 --build-arg 給值的時候都拿得到。

FROM alpine

ARG foo
RUN echo ${foo}

FROM alpine

ARG foo
RUN echo ${foo}

今日自我回顧

今天一連串的實作,相信讀者能更了解 ENV 與 ARG 的差異,以及適用的情境。

使用 ENV 可以讓 Dockerfile 更好維護,而 ARG 則是可以讓同一份 Dockerfile 產生更多不一樣的 image。讀者可以視情況運用這兩個指令。