---
# System prepended metadata

title: Dockerfile
tags: [docker]

---

###### tags: `docker`
# Dockerfile
Docker會讀取`Dcokerfile`的指令自動建立image，`Dockerfile`為一文件涵蓋了所有使用者可以建立image的指令集，以下介紹各種可以用在`Dcokerfile`的指令。

## Format
這份教學是不分大小寫的，但是為了容易辨別以下會使用大寫作為區分。。
Docker會按照順序讀取`Dockerfile`裡面的指令，一個`Dcokerfile`必須由`FROM`開頭，`FROM`指令表達了從哪一個母Image為根基，只有`ARG`能被寫在`FROM`指令前面。
Docker會將`#`視作註解符。


## FROM
`FROM`指令會初始化一個build階段並且為接下來的指令設定一個母image，一有效的`Dockerfile`會必須是由`FROM`指令為開頭，母image可以為任何有效的image。
- `ARG`是唯一一個指令可以在`FROM`前出現的。
- `FROM`在單一`Dockerfile`中可以出現好幾次用來建立多個images或是使用其中一個為另一個image的bulid stage。
- Optionally可以在`FROM`後加上`AS name`，這個name可以被使用在後續的`FROM`和`COPY --from=<name>`指令上用來標示出是哪個image要被用在建立這個stage上。
- `tag`和`digest`也是optionally，省略的話會自動以`latest`為預設

### Understand how ARG and FROM interact
`FROM`指令支援了`ARG`宣告的變數。
```dockerfile=
ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras
```
在`FROM`前宣告的`ARG`是屬於build stage之外的，所以它沒辦法被使用在`FROM`後面的任何指令上，為使用第一個`FROM`前的`ARG`宣告的值，可以使用一個不帶有值的`ARG`指令。
```dockerfile=
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
```

## RUN
RUN有兩種形式:
- `RUN <command>` (shell form, the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows)
- `RUN ["executable", "param1", "param2"]`(exec form)

`RUN`指令會在新的layer上針對目前的image執行任何指令並且遞交結果，結果會被用在`Dockerfile`的下一步。
`RUN`指令的分層以及遞交結果符合了Docker的核心概念就是階段控制是很簡單的且容器可以在images裡面的任何階段被建立，就像是版本控制一樣。

## CMD
`CMD`指令有三種格式
- `CMD ["executable","param1","param2"]` (exec form, this is the preferred form)
- `CMD ["param1","param2"]`(as default parameters to ENTRYPOINT)
- `CMD command param1 param2` (shell form)

`Dokcerfile`只能夠有一個`CND`指令，如果使用了超過一個的`CMD`指令只有最後一個`CMD`會有作用。
`CMD`最主要的目的就是提供一個預設值給執行中的container，這些預設值可以包含可被執行的，或是省略可執行的，但就要提供一個`ENTRYPOINT`指令。如果`CMD`是用來提供`ENTRYPOINT`預設值參數的話，則兩個指令都要使用JSON array format。

如果有特別在`docker run`時特別另外寫參數，則會覆蓋掉`CMD`的預設值
:::info
別將`RUN`和`CMD`搞混了，`RUN`實際上會執行指令並且commit結果，`CMD`並不會在建立階段執行任何東西而是給了image預期的指令。
:::

## LABEL
```dockerfile=
LABEL <key>=<value> <key>=<value> <key>=<value> ...
```
`LABEL`指令為image加入metadata，`LABEL`是key-value paie的寫法，為在`LABEL`中使用空格，要使用引號或倒斜線，以下一些範例：
```dockerfile=
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
```

## MAINTAINER(棄用)
官方建議使用`LABEL`來做標註image的維護者會是更有彈性的作法。例如使用`docker inspect`時，設定以下方式會與`MAINTAINER`相呼應：
```dockerfile=
LABEL org.opencontainers.image.authors="SvenDowideit@home.org.au"
```

## EXPOSE
```dockerfile=
EXPOSE <port> [<port>/<protocol>...]
```
`EXPOSE`指令指定的Docker在runtime階段要監聽哪一個網路接口，你可以設定為TCP或是UDP，預設值是TCP如果沒有特別設定的話。
`EXPOSE`指令並非真正發布這個port，其真正的作用在於文件上的形式為建立這image和使用並啟動這container的人之間說明是哪一個port會被使用，為了真正在跑container時使用這個port，在`docker run`時要使用`-p`標籤。

## ENV
```dockerfile=
ENV <key>=<value> ...
```
`ENV`指令設定了環境變數`<key>`為`<value>`的值，這個value會是接下來build stage中指令的環境變數。

```dockerfile=
ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy
```
`ENV`指令允許多個`<key>=<value> ...`例如:
```dockerfile=
ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
    MY_CAT=fluffy
```
當一個container從一個被建立後的image運行後，使用`ENV`建立的環境變數會被保留，可以使用`docker inspect`來觀察，並且使用`docker run --env <key>=<value>`來變更。

永久性的環境變數可能造成一些壞處，例如設定`ENV DEBIAN_FRONTEND=noninteractive`改變了`apt-agt`的行為，並有可能混淆image的使用者。
如果一個環境變數是只有在建立階段是被需要的而非在最終的image，考慮將其放在單一指令例如下面:
```dockerfile=
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y ...
```
或是使用`ARG`，這也不會保存到最後階段的image上
```dockerfile=
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y ...
```

## ADD
ADD 有兩種寫法
```dockerfile=
ADD [--chown=<user>:<group>] [--checksum=<checksum>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
```
`ADD` 指令會從`<src>`複製新的檔案、路徑或是遠程文件並將它們加入到image的檔案系統路徑`<dest>`。
多個`<src>`是可以被設定的但如果他們是檔案或是路徑，他們的路徑會被當作這個build的內容的來源。
個別`<src>`可以是通配符例如：
將所有hom開頭的檔案加入
```dockerfile=
ADD hom* /mydir/
```
下面是替換單一字母
```dockerfile=
ADD hom?.txt /mydir/
```

`<dest>`是一組絕對路徑或是`WORKDIR`的相對路徑，就是資源會被複製到的container的地方。
以下例子使用相對路徑，會將"test.txt"複製到`<WORKDIR>/relativeDir/`:
```dockerfile=
ADD test.txt relativeDir/
```
以下是絕對路徑，會將"test.txt"複製到`/absoluteDir/`
```dockerfile=
ADD test.txt /absoluteDir/
```

## COPY
`COPY` 指令會從`<src>`複製新的檔案、路徑並將它們加入到image的檔案系統路徑`<dest>`。

## VOLUME
```dockerfile=
VOLUME ["/data"]
```
`VOLUME`指令會建立一個映照點(mount point)有自訂的名稱並標註其為存儲本地主機或其他container的外部資料。 其值可以是JSON陣列`VOLUME ["/var/log/"]`,或是字串有著多個參數例如，`VOLUME /var/log` or `VOLUME /var/log /var/db`。