Skip to main content

Command Palette

Search for a command to run...

Dockerfile best practices

A collection of all Dockerfile best practices

Published

I am not a programmer just a leaner. Writing JavaScript, Python, and Go and doing something on Kubernetes.

這邊會分幾個大項目來做分類

權限

Rootless containers

很基本的,你不應該使用 Root 的 User 來操作 entrypoint。所以建議一定要設定 USER 來避免使用 Root。並且在程式主要讀寫的檔案位置,給予適當的權限

FROM alpine:3.12
# Create user and set ownership and permissions as required
RUN adduser -D myuser && chown -R myuser /myapp-data
# ... copy application files
USER myuser
ENTRYPOINT ["/myapp"]

如果 container 需要使用到 root 的話,也會建議使用 sudogosusu-exec 等工具。

Don't bind to a specific UID

不要替 non-root user 設定特定的 UID。設定特定的 UID 會需要調整所有綁定的權限,這可能會造成檔案讀取的錯誤。不要用 hardcore 的方式,需要有彈性的處理。

Make executables owned by root and not writable

建議在 container 中所有可執行的檔案都由 root 所擁有,即使是由 non-root user 來執行的檔案。另外也不應該具有可寫的權限。

...
WORKDIR $APP_HOME
COPY --chown=app:app app-files/ /app
USER app
ENTRYPOINT /app/my-app-entrypoint.sh

以此範例下,app user 不過需要擁有權,只需要執行權而已。所以 COPY ... 這段就完全不需要。

減少攻擊範圍

此主題主要目標是要盡量的減少 image 大小,避免包含到不必要的 packages 或者開放 port,來增加可被攻擊的範圍。

Multistage builds

使用 multistage build 會建立一個中間 image(或者 stage),其中包含了所有所需用來編譯或是生產你最終產物(例如:最後的執行檔)的工具。然後你再複製其最終產物到 final stage,其中不包含額外的開發相依套件、暫時檔等。
最後的 image 將只會留下所需的檔案,而不會留下暫存檔案、編譯時所需的相依套件。這樣可以大大減少可被攻擊的範圍,同時也能降低 image 的大小。

# This is the "builder" stage
FROM golang:1.15 as builder
WORKDIR /my-go-app
COPY app-src .
RUN GOOS=linux GOARCH=amd64 go build ./cmd/app-service

# This is the final stage, and we copy artifacts from "builder"
FROM gcr.io/distroless/static-debian10
COPY --from=builder /my-go-app/app-service /bin/app-service
ENTRYPOINT ["/bin/app-service"]

這邊有兩個範例提供參考

Distroless, from scratch

使用最小所需的 base image。理想上,我們從頭開始建立 image,但只有 100% 二進制檔案也依舊可以運作。
Google 所提供的 Distroless 會是個不錯的選擇。這主要被設計用來只包含最小的 library (例如:glibc, libssl, openssl) 來跑 Go、Python 和其他的框架

Use trusted base images

  • 應該要使用可信任來源 repositories 認證和官方的 image
  • 當使用客制的 image 時,確認其來源以及 Dockerfile。並且建立你自己的基礎 image。因為無法保證從 public registry 是從給予的 Dockerfile 建立來的,也不能保證是最新的。
  • 有時候在安全性和極簡主義下,官方 image 可能並不符合。

Update your images frequently

要保持你的基礎 image 頻繁的更新,新的安全漏洞會持續的被發現。所以要保持更新這些安全性的修正,但是並不是說需要得次都更新到最新的版本,因為可能會包含一些重大的更改,但需要定義下更新的策略:

  • 堅持使用穩定或長期支援的版本,這些版本會很快並經常提供安全性的修復
  • 提前計畫:準備好在你的基礎 image 達到其生命週期結束並停止更新之前刪除舊版本並更新
  • 定期重建你的 image,並使用類似的策略來更新 package,例如 npmgo mod

Exposed ports

任何開放的 port,都是一個可直接攻擊你系統的大門。所以要只開放你應用程式所需的 port,並且避免開放 SSH(22)。
請注意,即使 Dockerfile 有提供 EXPOSE。但是這指令只是用於說明、標示的用途,並不會自動允許其 EXPOSE 指定的 port 可以連線。

避免機密資料外流

Credentials and confidentiality

不要將任何的密碼或機密資料放到 Dockerfile 的指令(環境變數、args 或是 hardcode 到指令)
另外要特別注意,不要將其機密檔案複製到 image 中,即使後來被指令刪除也一樣。因為依舊可以在之前的 layer 中被讀取,它並沒有真正的刪除,只是對於最終的檔案系統被隱藏起來。

  • 如果應用程式支援從環境變數來設定配置,則會建議在執行時設定其密碼,或是使用 Docker secrets, Kubernetes secrets 提供其值到環境變數。
  • 使用設定檔並且建立掛載到 docker 中的設定檔,或是從 kubernetes secret 中掛載

應該要讓應用程式可以在 runtime 時可以客製化的 injecting 值到特定的密碼。

ADD vs COPY

ADDCOPY 基本上提供相同的功能,但是 COPY 更為明確。
建議盡量使用 COPY,除非有額外需要使用 ADD,像是需要從 URL 來新增檔案或是從壓縮檔(因為 ADD 會自動解壓縮)。 但是一般情況下,也會比較常使用 RUN 來代替 ADD 做這些操作。

Build context and dockerignore

這是使用 docker 執行建構時的典型操作,使用預設 Dockerfile 和當前目錄中的上下文:

docker build -t app .

注意!

使用 . 這個參數是架構的內容,這會是相當危險的。因為可能會複製了機密資料或是任何非必要的檔案到 image。

會建議使用一個子目錄,將所需的東西放在裡面。
此外也建議可以建立一個 .dockerignore 來明確指定哪些檔案或目錄不要放到 image 中。

將你的建構內容放到你自己的目錄,並使用 .dockerignore 來減少各種放入非必要檔案的可能

其他

Layer sanity

要記得指令的順序在 Dockerfile 相當的重要。自從 RUNCOPYADD和其他指令將會建立一個新的 image layer,結合多個指令再一起可以減少數個 layers

原本:

FROM ubuntu
RUN apt-get install -y wget
RUN wget https://…/downloadedfile.tar
RUN tar xvzf downloadedfile.tar
RUN rm downloadedfile.tar
RUN apt-get remove wget

優化後:

FROM ubuntu
RUN apt-get install wget && \
     wget https://…/downloadedfile.tar && \
     tar xvzf downloadedfile.tar && \
     rm downloadedfile.tar && \
     apt-get remove wget

並且將較不會頻繁更改的指令,放置更前面,便於 cache

原本:

FROM ubuntu
COPY source/* .
RUN apt-get install nodejs
ENTRYPOINT ["/usr/bin/node", "/main.js"]

優化後:

FROM ubuntu
RUN apt-get install nodejs
COPY source/* .
ENTRYPOINT ["/usr/bin/node", "/main.js"]

Metadata labels

Label 有助於 image 的維運和管理,像是應用程式的版本、網站的連結、維護者的聯絡方式等

您可以查看 OCI 圖像規範中的預定義註釋,這些註釋棄用了之前的 Label 架構標準草案

Linting

一些工具例如 Haskell Dockerfile Linter(hadolint) 可以偵測 Dockerfile 中不好的實踐,甚至是 RUN 指令中的操作。

Locally scan images during development

Image scanning 是另一個你再啟動你的 container 之前偵測潛在問題的方式。可以按照 image scanning best practices,你應該要在每個 stage 的 image 生命週期中去 scanning。

Image 建立之後

此前,我們都專注在 image 建構的過程。在此將專注在建構之後,啟動的部分

Image Lifecycle

Docker port socket and TCP protection

確認 /var/run/docker.sock 有正確的權限,而且如果 docker 有透過 TCP 開放(完全不建議),請確認有適當的保護

Sign images and verify signatures

在最佳實踐中,很重要的一塊就是使用 docker content trust、Docker notary、Harbor notary 或是類似的工具對你的 image 做數位簽證在 runtime 時驗證
在每個 runtime 時啟用簽名驗證是不同的。例如:在 Docker 中可以設定環境變數的方式來啟用 export DOCKER_CONTENT_TRUST=1

Tag mutability

tag 是很容易做變動的,可以參考這篇 Attack of the mutant tags

Run as non root

雖然在 Dockerfile 中我們可以設定 non-root user,但是其執行環境(docker run 或 Kubernetes),有最終的決定權。所以要避免其執行環境使用的到 root user。

Include health / liveness checks

盡量在 Dockerfile 中包含 HEALTHCHECK 指令。如果是在 Kubernetes 的話,會建議設定 livenessProbe,因為 docker 的 HEALTHCHECK 將不會被執行

Drop capabilities

在運行中,您可以使用 Docker 中的 --cap-drop 或 Kubernetes 中的 securityContext.capabilities.drop應用程序功能限制為所需的最小集合。 這樣,萬一您的容器遭到破壞,攻擊者可採取的行動範圍就會受到限制。

References

  • https://sysdig.com/blog/dockerfile-best-practices/

More from this blog

如何開始入門軟體工程領域 - 名詞解釋(長期更新)

現在應該開始有很多人想要踏入軟體工程的領域,但在進入這個領域之前,覺得先了解一些名詞,可以在入門時更有方向也更知道要用什麼關鍵字去找尋有用的資訊。這篇文章就是想要幫助想要入門的人理解一些軟體工程裡的專有名詞。 作業系統 這一區塊主要解釋跟作業系統層面相關的名詞 英文中文解釋 Operation system 簡稱 OS | 作業系統 | 就是電腦的作業系統,是三大作業系統分別是:Linux、Windows、macOS | | Linux | | 自由和開放原始碼的 UNI...

May 10, 2023

我的 MacBook Pro (Apple Silicon) 設定

現在開始因為 ChatGPT 的出現,各種 AI 助手的功能都跑出來了。想想自己用了許久的環境設定也應該要來重新審視和建立新的開發環境了,僅此紀錄我個人的環境配置步驟和設定。 環境前置步驟 還原 MacBook Pro 至全新環境 macOS(全部資料刪除) 設定好初始設定後,登入 Apple ID 進入 App Store 確定 macOS 版本和預設 APP 都更新到最新 macOS 版本 到系統設定調整所有設定至個人習慣的設定 三指拖移 觸控板手勢開啟 防火牆開啟 輸入法設定...

Apr 25, 2023

ChatGPT 下的發展預想

從 ChatGPT 問世到現在,有許許多多的文章和討論出來。先從最早的 Google 要完蛋了,到後來的工作要被取代了,工程師失業了。 我就比較沒有想要馬上出來評論一下,我喜歡讓子彈飛一會兒。跟討論一下我自己比較在意的討論點。 Google 為什麼慢了? 結論:因為他需要更小心 很多人說 Google 怎麼被微軟搶先了一步。剛開始 Bing 說要加上 AI 的時候大家都在說 Google 怎麼慢了。我就馬上跑去看 OpenAI 的網站,靠北呀啊就 Azure 贊助的。那當然在正式上線 ChatG...

Mar 23, 2023

不工程的攻城獅

223 posts

I am not a programmer because I am not good at programming. But I do programming. Love to learn new things. An animal lover and a dancer. My oshi is 潤羽るしあ.

Dockerfile best practices