Dockerfile的最佳實踐
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