1. Create ephemeral containers(容器是短暫的)

透過Dockerfile建構的鏡像應該盡可能是短暫的,短暫意味著可以很快速的啟動和銷毀

2. Understand build context(了解建構上下文)

避免使用Path為/做為建構的上下文,它會導致建構將硬碟全部內容傳輸到守護進程

  • 建構時注意context的目錄,通常為.當前的位置,以下範例: docker build .
  • 善用.gitignore來排除某些文件,如:
# .dockerignore
node_modules
bar

3. Pipe Dockerfile through stdin(借stdin管道傳輸Dockerfile)

你如果鏡像只是暫時的一次性建構,且又懶的寫Dockerfile,可以由以下寫法表示:

echo -e 'FROM busybox\nRUN echo "hello world"' | docker build -
docker build -<<EOF
FROM busybox
RUN echo "hello world"
EOF

4. Use multi-stage builds(使用多階段式的建構)

使用多階段建構可以大幅減少最終鏡像的大小,簡單說就是只要在乎最終鏡像的大小,而中間階段的階段就不用考慮優話。
以下使用熟須的golang做為例子,建構過程中,我們需要將golang主程式編譯成二進位文件,接著再把編譯後的文件丟到一個極度輕量的alpine容器內,這樣就可以確保最終的鏡像是非常輕量化的。
# build stage
FROM golang:1.18.0-alpine AS build
RUN apk add git
# 複製原始碼
COPY . /go/src/project
WORKDIR /go/src/project
# 進行編譯(名稱為:runner)
RUN go build -o runner

# final stage
# 運行golang 的基底
FROM alpine:3.17
# 使用alpine容器 複製第一階段映像檔
COPY --from=build /go/src/project/runner /app/runner
COPY ./config /app/config
# 設定容器時區(亞洲/台北)
RUN apk update 
# 工作目錄
WORKDIR /app
# 執行編譯檔案
ENTRYPOINT [ "./runner" ]

5. Don’t install unnecessary packages(不要安裝不必的包)

為了降低複雜性,減少依賴性,減少文件大小並縮短建構時間,在非必要情況應避免

6. Minimize the number of layers(鏡像層數減少)

在舊版Docker中,盡量減少鏡像中的層數,以確保他們的性能

  • 只有RUN,COPY,ADD創見層,其他指令創建臨時中間鏡像,並不會增加建構的大小
  • 在可能的情況下,使用多階段建構,可以參考上方使用多階段建構

7. Sort multi-line arguments(將多行參數進行排序)

將多行參數按照字母排序,有助於避免重複安裝到同一個包,更新列表包時也更容易維護,以下範例使用反斜槓\也更有幫助

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion \
  && rm -rf /var/lib/apt/lists/*

8. Leverage build cache(利用建構緩存)

建構鏡像時,Docker會逐步執行Dockerfile中的指令,並按照順序執行,在每條指令執行前,Docker都會在緩存中查找有是否有存在可以重用的鏡像,不重複創建

可以在docker build 命令中使用 --no-cache=true選項來禁用緩存

  • 從緩存中已有的父鏡像開始,將下一條指令與該父鏡像的所有子鏡像進行比較,檢查這子鏡像,在被創建時的指令是否和被檢查的指令完全一樣。如果不是,則緩存無效。
  • 在大多數的情況下,只需要簡單的比對 Dockerfile 中的指令與子鏡像

一旦緩存失效,所有後續的Dockerfile命令都會生成新的並不會使用緩存

Dockerfile寫法指引

FROM

盡可能使用官方當前的鏡像,Docker推薦 Alpine鏡像,因為它受嚴格控制且體積小(不到6MB),完整性也足夠

LABEL

LABEL的用處就好比我們在寫README.md文件中寫How-TO意思一樣,有助於開發者更能知道鏡像本身的資訊,使我們的鏡像更透明

# 以下為LABEL範例
# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""
# 以下用法也是可以被接受的
# Set multiple labels on one line
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

# Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME\ Incorporated \
      com.example.is-beta= \
      com.example.is-production="" \
      com.example.version="0.0.1-beta" \
      com.example.release-date="2015-02-12"

RUN

使用 \ 可以提昇Dockerfile可讀性,可維護性

RUN /bin/bash -c 'source $HOME/.bashrc && \
echo $HOME'

apt-get

使用apt-get的時候要注意,永遠使用RUN apt-get udpate 而不是單一使用apt-get update

使用RUN的時候注意以下錯誤,以下結果將會導致apt-get update沒有被執行,因為建構使用了緩存的版本

# syntax=docker/dockerfile:1
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl

你需要只用 RUN apt-get update && apt-get install -y來確保Dockerfile安裝最新的安裝包,這技術稱為緩存破壞,也可以透過指定的包版本來實現緩存清除,如:

RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
 && rm -rf /var/lib/apt/lists/*

CMD

CMD的結構永遠都是CMD ["executable", "param1", "param2"…]

詳細查看 Dockerfile reference for the CMD instruction

EXPOSE

EXPOSE指令指示容器監聽的端口,例如Apache Web服務器監聽端口使用 EXPOSE 80

詳細查看 Dockerfile reference for the EXPOSE instruction

ENV

添加服務的環境變數,可以使用ENV替容器安裝程序更新PATH環境變數,例如設置:

ENV PATH=/usr/local/nginx/bin:$PATH
#來確保可以正確執行
CMD ["nginx"]

詳細查看 Dockerfile reference for the ENV instruction

ADD or COPY

雖然說ADD,COPY在功能上非常相似,但COPY還是首選,因為比ADD更為透明,COPY只支持將本地文件複製到容器中,一般來說最常使用ADD的情況為ADD rootaa.tar.xz

應避免以下寫法

ADD https://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all

使用以下寫法

RUN mkdir -p /usr/src/things \
    && curl -SL https://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

ENTRYPOINT

使用ENTRYPOINT的最好方式為設置為主要命令,然後將CMD作為預設參數選項:

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

詳細查看 Dockerfile reference for the ENTRYPOINT instruction

VOLUME

VOLUME為需要容器掛載持久的儲存區,為了解決容器間資料共享與資料保存用,例如:

# 為/myvol設置一個持久的儲存區
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
# 將myvolume:/data nginx /bin/sh
docker run -it -v myvolume:/data nginx /bin/bash

詳細查看 Dockerfile reference for the VOLUME instruction

USER

使用範例:

USER <user>[:<group>]
# or
USER <UID>[:<GID>]

注意:當有指定使用者群組時,其他任何未指定的群組成員都將會忽略,如果沒有指定群組,鏡像將會使用root群組

詳細查看 Dockerfile reference for the USER instruction

WORKDIR

WORKDIR /path/to/workdir

WORKDIR 指令為RUN,CMD,ENTRYPOINT,COPY,ADD指令設置工作目錄,即使WORKDIR不存在,Dockerfile也會自行創建默認工作目錄/,但避免在未知的目錄進行意外操作,最好還是設置WORKDIR

參考

詳細查看 Dockerfile Best Practices

感謝大家閱讀,初次寫技術類文章希望內容能盡量完整,如發現內容有誤也歡迎底下留言給我,另外覺得內容不錯的話順手給我一個讚吧!你的讚將為技術寫手小白注入更多的成長動力,感謝各位,一起學習一起交流成為更好的自己。