昨天練習了怎麼啟動 container,也跑了第一個 web server,今天來試著把自己的 html 做成 image ㄅ!
首先跟昨天一樣開一個獨立的資料夾放今天的東西,裡面再開一個 site 資料夾放網頁:
mkdir -p ~/ironman-lab/day08/site
cd ~/ironman-lab/day08
先在 site 裡建一個 index.html,用 VSCode 或終端機都行,內容隨意。我這邊就打:
<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>Hello from my container</h1>
<p>這個檔案在我的 Mac 上,nginx 在容器裡。</p>
</body>
</html>
接著在終端機下:
docker run -d --name web -p 8080:80 -v ~/ironman-lab/day08/site:/usr/share/nginx/html nginx:alpine

-v 是 --volume 的縮寫,格式是 -v 外面:裡面,它把 Mac 上的 ~/ironman-lab/day08/site 接到 container 裡的 /usr/share/nginx/html,也就是昨天 ls 看到放 index.html 的那個資料夾。接上之後,container 裡的那個路徑就是你電腦上的資料夾,不是複製一份過去,所以在 Mac 上改檔案,container 裡立刻看得到。方向跟 -p 一樣。-v 是 docker 的參數,跟昨天 node -v 的 -v(版本)長得一樣但完全無關。docker images 觀察docker images # 沒有新東西
docker rm -f web # 看完收掉,等一下要用同一個名字

day08 資料夾裡(不是 site 裡)新增一個檔案,檔名就叫 Dockerfile,沒有副檔名。內容只有兩行:FROM nginx:alpine
COPY site/ /usr/share/nginx/html/
FROM:以哪個 image 當底。昨天跑過的 nginx,這裡用 alpine 版比較小。COPY <外面> <裡面>: 把 site 資料夾裡的東西複製進 image,放到 nginx 放網頁的那個位置。方向還是先寫自己這邊。docker build -t my-site:v1 .
docker images


docker build:照 Dockerfile 的步驟做出一個 image。-t my-site:v1:給它名字和版本,名字只能小寫。.:告訴 Docker「以現在這個資料夾當範圍」,Dockerfile 和 COPY 的路徑都從這裡算。這個點很常被漏掉,漏了會報錯喔 ~docker images 觀察,會看到多一個 my-site,大小差不多就是 nginx:alpine 再加幾 KB。docker run -d --name web -p 8080:80 my-site:v1
開 http://localhost:8080,同一個頁面,但這次頁面在 image 裡,不在你電腦的資料夾裡。
現在把 site/index.html 的標題改成「Hello world v2」,存檔,重新整理瀏覽器:沒變,還是原來的字。
剛才用 -v 掛資料夾時改了會立刻變,這次不會,因為 container 用的是 build 當下複製進 image 的那一份,之後你在電腦上改什麼都跟它無關。image 就是一張快照。
要讓新內容生效,就得再 build 一次,這次給它 v2:
docker build -t my-site:v2 .
docker rm -f web
docker run -d --name web -p 8080:80 my-site:v2
docker images

重新整理,變成 「Hello world v2」 了。docker images 裡 my-site 有 v1 和 v2 兩個,v1 沒有被蓋掉。
這就是用tag 管理版本:冒號後面的 v1、v2 就叫 tag。這次修改內容後,我們用 v2 標記新 image,原本的 v1 也保留下來;如果 v2 出問題,就可以改用 v1 重新建立 container。
不過 tag 可以被重新指定,所以要保留版本,就不要重複拿同一個 tag 標記不同內容。之後部署到 AWS 時,我們會用 Git commit 的編號當 tag,方便追蹤部署的是哪一版程式。
今天做了三件事:把資料夾掛進去、把頁面包進 image、再 build 一次出 v2。掛進去的搬不走,包進去的到哪都能跑,這就是 Dockerfile 存在的理由。
明天把自己的 Next.js 專案做成 image。另外你可能注意到 build 輸出裡的 [1/2]、[2/2],那是建置步驟的編號。這些步驟背後還牽涉到 image 的分層和 cache,明天會講為什麼 Dockerfile 的指令順序會影響 build 速度。
資安小提醒:COPY 會把指定來源裡的檔案包進 image,今天複製的是 site/。明天處理整個專案、使用 COPY . . 時,就要注意別把 .env、金鑰檔或不需要的 node_modules 一起包進去。image 之後還會推到雲端的 registry,所以 build 前要確認複製範圍,並用明天會介紹的 .dockerignore 排除不該帶入的檔案。
Dockerfile 官方文件
docker build 官方文件