MOOSSin
選單

全部文章 · 開源工具

Docker 與 Compose:把一個 commit 封起來,然後盡量限制它

對一台機器上的幾個服務來說,Docker 的價值在封裝與隔離,不在編排。這篇談用 commit 當映像標籤、容器該被拿走哪些能力、Compose 認領容器的規則,以及 Windows 上 Docker Desktop 的一個前提。從便當盒講起。

「在我電腦上明明可以跑。」

這句話大概是所有一起做過專題的人最熟悉的一句。你花了一整晚把東西弄好,傳給組員,組員打開,紅字噴滿螢幕。不是你的錯,也不是他的錯,是你們兩台電腦裡裝的東西不一樣:Python 版本差一號、某個套件他沒裝、某個環境變數只存在於你的機器。Docker 想解決的就是這件事,而它解決的方式非常直白:把你的電腦裡跟這個程式有關的那一小塊,整個打包帶走。

便當盒理論

我喜歡把 Docker 映像想成便當。你不是把「食譜」給別人,要他自己去買菜、自己煮;你是把煮好的便當整個裝盒交出去,裡面有飯、有菜、有筷子,連醬油包都放好了。對方打開就能吃,不用問你「你們家的醬油是哪一牌」。

映像就是那個便當盒,裡面裝著程式碼、直譯器、相依套件和系統函式庫。容器是打開來吃的那一次。同一個便當盒可以打開很多次,每一次都一樣。Docker Engine 是 Apache-2.0 的開源專案,Compose 是用一個 YAML 檔描述幾個便當怎麼一起上桌的工具。

這篇不談叢集,不談那種幾百台機器的排程。只談一台機器上跑幾個服務的用法,那是大多數個人專案的實況,也是我自己的實況。

標籤用 commit,不用 latest

映像的標籤是你自己決定的字串。latest 是預設值,也是最糟的選擇。它的意思是「最新的那一個」,但最新是相對於什麼時候?今天的 latest 和上星期的 latest 可能完全不同,而你不會知道。這就像便當盒上只寫「今日特餐」,你永遠不知道打開會是什麼。

我的做法是把建置那個映像的 Git commit 完整雜湊當標籤,目前線上是哪一個記在一個檔案裡,回滾就是把指向改回上一個標籤。Dockerfile 可以在開頭檢查傳進來的版本字串是不是四十個十六進位字元,不是就拒絕建置,這樣就不會有標著佔位字串的映像流出去。有一次我差點用 test 當標籤建了一個映像,被自己寫的檢查擋下來,那種感覺很微妙,像被過去的自己敲了一下頭。

基底映像也一樣,用 digest 釘死,例如 python:3.13-slim-bookworm@sha256:…。同一個標籤明天可能指向不同的內容,digest 不會。

容器預設拿到的東西太多

這件事很多教學不會講,因為講了就不是「五分鐘上手」了。一個容器預設以 root 執行、根檔案系統可寫、帶著一整組 Linux capability。也就是說,便當盒打開之後,裡面的東西預設有你家的全部鑰匙。

Compose 檔裡幾行就能收回來:user: 指定非 root 的 UID,read_only: true 讓根檔案系統唯讀,cap_drop: [ALL] 丟掉所有能力,security_opt: [no-new-privileges:true] 擋提權,pids_limit 限制行程數,tmpfs 給 /tmp 一塊有大小上限、不可執行的暫存空間。需要寫的目錄用 bind mount 明確掛進來,能唯讀就加 :ro。

只跑一次的工作,我習慣加 --network none。匯入資料、跑一支轉換腳本,這些事沒有理由連外。給它網路,就像讓來修水管的師傅順便有你的 Wi-Fi 密碼,多數時候沒事,但沒事不是理由。

Compose 只認自己建的容器

Compose 用標籤辨認哪些容器屬於它的專案,但光有標籤不夠,它還會比對一個由設定算出來的 config-hash。用 docker run 手動起的容器,就算名字和標籤都對,compose up 也不認,會想另建一個然後撞名失敗。

這件事我是在急救的時候學到的。當時有個服務掛了,我為了快,直接 docker run 手動起一個,服務回來了,我很得意。隔天想用 Compose 正常部署,它一臉「這誰?」的表情拒絕我。最後是把手動起的容器刪掉,讓 Compose 重建。急救時用 docker run 沒問題,事後要記得把現場還原,不然下一次部署會在奇怪的地方停住,而那個時候你已經忘了自己做過什麼。

映像裡的一個小陷阱

如果映像的 ENTRYPOINT 已經是直譯器,docker run image python -m foo 會變成「用直譯器執行一個叫 python 的檔案」,然後找不到。參數要從 -m 開始。

這種錯在文件審查時看不出來,你看著指令,每個字都對,跑一次就現形。我花了二十分鐘懷疑映像壞掉,最後發現只是多打了一個字。那天我學到的不是 Docker 的知識,是「先跑一次再懷疑世界」。

小劇場:要不要學 Docker

「我一個人做的小專案,需要學 Docker 嗎?」

「你會不會換電腦?」

「明年畢業應該會。」

「那你明年會很感謝現在的自己。因為到時候你要在新電腦上重建整個環境,而你已經忘了當初裝了什麼。」

「所以 Docker 是給未來的自己寫的說明書。」

「是說明書,而且是會自動執行的那種。」

「聽起來很好,但為什麼大家都說它很難?」

「因為大家一開始就想學叢集那一套。你先學便當盒就好,桌菜之後再說。」

Windows 上的前提

Docker Desktop 在 Windows 上不管用 WSL2 還是 Hyper-V 後端,都要啟用 Hyper-V 的虛擬化層。如果同一台機器上有 VirtualBox 的虛擬機在跑重要的東西,啟用之後 VirtualBox 會退到一個慢很多的相容模式。兩者只能選一個當主角。我選了 VirtualBox,所以 Docker 只活在虛擬機裡面,Windows 端一個容器都沒有。這不是什麼高深的決定,只是兩個室友都想用同一張書桌,總要有一個去圖書館。

我對它的定位

在一台機器上,Docker 是打包和隔離工具,不是編排系統。每個映像回答「這是哪一版」,每個容器回答「它能碰什麼」,Compose 回答「這幾個怎麼一起起來」。這三個問題答清楚,剩下的複雜度大多不需要。

至於「在我電腦上明明可以跑」這句話,用了 Docker 之後它不會消失,只是會變成「在我的容器裡明明可以跑」。差別是這次你可以把容器整個給對方,讓他自己看。

幾個關於 Docker 的小知識

Docker 在 2013 年由一間叫 dotCloud 的公司發表,那間公司本來在做別的生意,Docker 只是他們內部的工具。工具紅了之後公司改名叫 Docker,原本的生意賣掉。這種「副產品比本業紅」的故事在軟體業不少,Slack 也是這樣來的。

容器和虛擬機的差別,一句話說完:虛擬機是整棟房子,連地基都是自己的;容器是同一棟房子裡的房間,共用地基和水電。所以容器啟動是秒級的,虛擬機是分鐘級的;也所以容器的隔離比虛擬機弱,因為你們共用同一個核心。這件事決定了很多安全上的取捨,包括為什麼前面要花那麼多篇幅講「把能力收回來」。

映像是一層一層疊起來的。你的 Dockerfile 每一行產生一層,下一層疊在上一層上面。兩個映像如果前幾層一樣,磁碟上只會存一份。這也是為什麼把「常變的東西」放在 Dockerfile 後面、「不常變的東西」放前面會讓建置變快:前面的層可以重用,只重建後面。

Docker 的鯨魚 logo 背上馱著一堆貨櫃,這個意象很準確,只是我每次看都覺得那隻鯨魚看起來有點累。

常見的三個誤會

第一個誤會是「用了 Docker 就安全了」。預設的容器以 root 執行、能碰的東西很多,安全是你設定出來的,不是 Docker 送的。

第二個誤會是「容器裡的資料會一直在」。容器刪掉,裡面寫的東西就沒了,除非你掛載了外部的目錄。很多人第一次丟資料就是這樣,docker rm 下去,資料庫不見了,然後在論壇發文問「Docker 是不是有 bug」。沒有,它只是照做。

第三個誤會是「映像很小」。python:3.13 那個基底映像將近 1 GB,slim 版本小很多,alpine 更小但有些套件裝不起來。硬碟不知不覺被舊映像塞滿是常態,docker system prune 這個指令值得記起來,跑一次通常能救回好幾 GB,雖然跑之前要看清楚它會刪什麼。

一個週末可以試的小練習

找一台有 Docker 的機器,跑 docker run --rm -it --read-only --user 1000:1000 --cap-drop ALL python:3.13-slim python。你會進到一個 Python 互動介面,試著 open("/test.txt", "w"),它會告訴你唯讀。試著 import os; os.getuid(),是 1000,不是 root。

然後拿掉那三個參數再跑一次,你會發現什麼都能做。兩次之間的差別,就是「一個預設的容器」和「一個被限制過的容器」的差別。花五分鐘體驗一次,比讀十篇文章都記得牢。順便看一下 docker images 裡那個映像有多大,然後想一下你需不需要那麼大。

後記

「在我電腦上明明可以跑」這句話,我後來想想,其實是一句很誠實的話。它承認了環境不同,只是說的人沒有意識到自己在承認什麼。Docker 做的事就是把那句話裡的「我電腦」打包起來交出去。至於打包之後還是跑不動的情況,那就是另一篇文章了。

官方網站:docker.com、docs.docker.com/compose

閱讀偏好

返回全部文章作者與內容更正

留言(0)

還沒有留言。讀完有想法,歡迎留下第一則。

留言經站長審核後才會顯示。暱稱和留言會公開,請不要寫下電話、地址等個人資料;資料怎麼處理,請見隱私權政策。