National Chengchi University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

政治大學 AQI 58 25°C PM2.5 12
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 07

寫 Dockerfile

FROM、RUN、COPY、CMD、EXPOSE:把環境寫成文字檔
Interaction 25 minute(s)

Learning Objectives

  • 規劃建立映像檔的流程:先在互動式容器裡手動做一次,記下每個步驟
  • 用 FROM 指定基底映像檔,用 LABEL 記錄維護者等資訊(取代已淘汰的 MAINTAINER)
  • 用 RUN 安裝軟體,知道每個 RUN 會產生一層,並把相關指令合併成一行
  • 用 COPY 把專案目錄裡的檔案放進映像檔,用 CMD 設定容器預設執行的程式
  • 說明 EXPOSE 只是文件,真正對應埠號要在執行容器時用 -p

把環境寫成一份文字檔

到目前為止,我們都是拿別人做好的映像檔來用。如果想要一個「裝好 nginx、放好自己網頁」的映像檔,可以怎麼做?

答案是寫一份 Dockerfile:一個純文字檔,一行一個指令,描述「從哪個映像檔開始、裝什麼、複製什麼、啟動時執行什麼」。 有了它,任何人在任何裝了 Docker 的電腦上都能用同一份文字檔建出一模一樣的環境,而且可以放進 Git 管理版本。

這一課的目標是做出投影片的 static_web 映像檔:一個用 nginx 提供靜態網頁的網站。

先手動做一次:規劃建立流程

寫 Dockerfile 之前,先決定三件事:

要決定的事 選項
作業環境 Debian/Ubuntu(用 apt-get 安裝套件)、Fedora(用 dnf)等
網頁伺服器軟體 Nginx、Apache httpd
基底映像檔 作業系統映像檔(例如 ubuntu:24.04,自己裝伺服器),或直接用伺服器的官方映像檔(例如 nginx)

接著開一個互動式容器,手動做一次並把成功的指令記下來,這份紀錄就是 Dockerfile 的草稿:

docker container run -it --name nginx_test ubuntu:24.04 /bin/bash

在容器裡:

apt-get update
apt-get -y install nginx
dpkg -l nginx                     # 確認套件裝好了
ls -l /var/www/html               # nginx 預設的網頁目錄
echo 'Hi, I am in your container' > /var/www/html/index.html
nginx -g "daemon off;"            # 在前景啟動網頁伺服器

最後一行的 daemon off; 很重要:nginx 平常會轉到背景執行,但容器的主程式一結束,容器就停止, 所以在容器裡要讓 nginx 留在前景。測試時可以另外開一個容器連過去看看(投影片用 alpine 容器執行 wget -qO - http://172.17.0.2), 看到自己寫的那行字,就表示流程可行。按 Ctrl+C 結束 nginx、exit 離開。

記下來的三個關鍵步驟:apt-get update、apt-get -y install nginx、nginx -g "daemon off;",再加上「把網頁放到 /var/www/html/」。

第一份 Dockerfile

建立專案目錄,裡面放兩個檔案:Dockerfile(沒有副檔名,D 大寫)與網頁 index.html。

static_web/
├── Dockerfile
└── index.html
# Version: 0.0.1
FROM ubuntu:24.04
LABEL maintainer="[email protected]"
RUN apt-get update && apt-get install -y nginx
COPY index.html /var/www/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
指令 作用
FROM 指定基底映像檔;必須是第一個指令(註解除外)
LABEL 在映像檔加上資訊(metadata),例如維護者、版本
RUN 建置時執行指令,例如安裝套件;結果存成新的一層
COPY 把專案目錄裡的檔案複製進映像檔
EXPOSE 說明這個映像檔預期使用的埠號(只是文件)
CMD 容器啟動時預設執行的程式

以 # 開頭的行是註解。下面逐一說明。

FROM 與 LABEL

FROM:新的映像檔要從哪個映像檔開始。

# 沒寫標籤就是 ubuntu:latest
FROM ubuntu
# 指定版本,建議這樣寫,日後重建結果才一致
FROM ubuntu:24.04
# 也可以用別人做好的映像檔
FROM mysql/mysql-server

LABEL:用「鍵=值」為映像檔加上資訊。值裡有空格時要用雙引號包起來:

LABEL maintainer="[email protected]"
LABEL version="1.0" description="A static web site served by nginx"

建好之後可以用 docker image inspect 在 Labels 欄位看到這些資訊。

舊寫法:投影片與舊教材常見 MAINTAINER Docker Education Team <[email protected]>。這個指令已被官方淘汰,改用 LABEL maintainer="…"。

ENV 也是常用的設定指令,用來設定環境變數,之後的指令與容器執行時都讀得到:

ENV APP_ENV=production

RUN:安裝軟體,每個 RUN 一層

RUN 在建置映像檔時執行指令,把檔案系統的變化存成新的一層,交給下一個指令繼續使用。

兩種寫法:

寫法 範例 說明
shell 形式 RUN apt-get update 用 /bin/sh -c 執行,可以用 &&、管線、$變數 等殼層語法
exec 形式 RUN ["apt-get", "update"] 不經過殼層,JSON 陣列,一定要用雙引號

幾個重點:

  • RUN 不能和你互動。安裝時會問「是否繼續?[Y/n]」的指令,一定要加 -y,否則建置會卡住或失敗。
  • 把 apt-get update 與 apt-get install 寫在同一個 RUN,用 && 連起來。分成兩個 RUN 時,update 那一層可能被快取住,日後只改 install 那行時,會拿舊的套件清單去安裝(第 08 課會看到快取怎麼運作)。
  • 每個 RUN 都會多一層。相關的步驟合併成一個 RUN,層數少、映像檔也比較好管理。
  • 只裝需要的東西。資料庫映像檔不需要文字編輯器,一般應用程式不需要網路除錯工具;裝得越多,映像檔越大、建置越久,要更新的套件也越多。
  • RUN 不會幫你啟動服務。它只記錄檔案的變化,不記錄執行中的程序;容器啟動時要執行的程式交給 CMD 或 ENTRYPOINT。

COPY:路徑相對於建置目錄

COPY index.html /var/www/html/
# 投影片的寫法:把整個 html 目錄的內容複製過去
COPY html/ /var/www/html/
# 路徑有空白時用 JSON 陣列寫法
COPY ["my file.txt", "/data/"]
規則 說明
來源路徑 相對於建置目錄(通常就是 Dockerfile 所在的專案目錄),不是主機的根目錄;COPY /src/webapp/ /opt/webapp/ 的 /src/webapp/ 也是指專案目錄裡的 src/webapp/
不能往外拿 建置目錄以外的檔案(例如 ../secret.txt)複製不到
萬用字元 COPY hom* /mydir/(hom 開頭的檔案)、COPY hom?.txt /mydir/(? 代表任一字元)
目的地以 / 結尾 視為目錄,檔案放進該目錄
相對的目的地 相對於 WORKDIR 設定的工作目錄,例如 COPY test relativeDir/
--chown=使用者:群組 指定複製後檔案的擁有者(只適用於 Linux 容器)

所以投影片才會建議:先建立專案目錄、切換進去、在裡面寫 Dockerfile,最後也在這個目錄執行建置指令。

放到哪個路徑,要看基底映像檔:

基底映像檔 nginx 從哪裡讀網頁
ubuntu:24.04 + apt-get install nginx /var/www/html/
官方的 nginx 映像檔 /usr/share/nginx/html/

路徑放錯不會出現錯誤訊息,網站照樣起得來,只是你看到的是 nginx 的預設歡迎頁。

CMD 與 EXPOSE

CMD 設定容器啟動時預設執行的程式,有三種格式:

格式 範例 說明
exec 形式(建議) CMD ["nginx", "-g", "daemon off;"] JSON 陣列、雙引號;直接執行程式,不經過殼層
shell 形式 CMD nginx -g "daemon off;" 用 /bin/sh -c 執行
當作 ENTRYPOINT 的預設參數 CMD ["-g", "daemon off;"] 搭配 ENTRYPOINT 使用(第 09 課)
  • 一份 Dockerfile 只有最後一個 CMD 有效。
  • 有了 CMD,執行時只要 docker container run ncuedu/static_web,不必每次打 nginx -g "daemon off;"。
  • 執行時在映像檔名稱後面加上指令,會取代 CMD:docker container run -it ncuedu/static_web /bin/bash 跑的是 bash,不是 nginx。
  • exec 形式不經過殼層,所以 CMD ["echo", "$HOME"] 會印出字面上的 $HOME,不會換成變數的值。

EXPOSE 說明這個映像檔預期使用哪些埠號:

# 預設是 TCP
EXPOSE 80
EXPOSE 80/udp

EXPOSE 不會真的開放埠號,它是寫給使用映像檔的人看的文件。容器的埠號預設都不對外公開, 真正的對應發生在執行容器時:-p 7777:80 指定主機的 7777 對到容器的 80;-P 則把 EXPOSE 列出的埠號都對應到主機上隨機的埠號(第 09 課)。

互動練習:組出一份 Dockerfile

按左邊的指令把它加到 Dockerfile 最後,用每行右邊的 ↑ ↓ 調整順序、✕ 刪除。完成三個任務:第一行用 FROM 指定基底映像檔;按 docker image build 建置成功;再按 docker container run -d -p 7777:80 執行,讓瀏覽器看到你的網頁,而不是 nginx 的預設頁。有兩條路都可以成功:ubuntu:24.04 要自己用 RUN 安裝 nginx、加上 CMD,網頁複製到 /var/www/html/;nginx:latest 已經內建 nginx 與預設的 CMD,網頁要複製到 /usr/share/nginx/html/。可以故意試試看:拿掉 CMD、放錯網頁路徑、或把 FROM 移到第二行,看看各會發生什麼事。右邊會畫出映像檔一層一層疊起來的樣子。

另一種寫法:從 nginx 映像檔開始

剛才的兩條路寫成 Dockerfile 是這樣:

# 寫法 A:從作業系統開始,自己安裝
FROM ubuntu:24.04
LABEL maintainer="[email protected]"
RUN apt-get update && apt-get install -y nginx
COPY index.html /var/www/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# 寫法 B:從 nginx 官方映像檔開始
FROM nginx:latest
LABEL maintainer="[email protected]"
COPY index.html /usr/share/nginx/html/

寫法 B 短很多,因為官方映像檔已經裝好 nginx,也寫好了 EXPOSE 80 與 CMD,你的 Dockerfile 會繼承這些設定。 寫法 A 能完全掌握安裝了什麼,適合學習每個步驟在做什麼,或需要另外安裝其他套件的時候。實務上常從官方映像檔開始,把心力放在自己的程式上。

想一想:Dockerfile 的指令

1.

Dockerfile 的第一個指令(註解除外)必須是哪一個?

2.

下面哪一種寫法比較好?

3.

COPY html/ /var/www/html/ 裡的 html/ 是指哪裡?

第 1 次作答

想一想:CMD 與 EXPOSE

1.

Dockerfile 裡寫了 EXPOSE 80,用 docker container run -d ncuedu/static_web(沒有 -p)啟動,從主機的瀏覽器連得到嗎?

2.

Dockerfile 裡依序寫了 CMD ["echo", "one"] 與 CMD ["echo", "two"],用 docker container run 啟動(不加其他參數)會印出什麼?

第 1 次作答

重點精華回顧

  • 先在互動式容器裡手動做一次、記下成功的指令,再把它寫成 Dockerfile。
  • FROM 必須是第一個指令,建議指定標籤(例如 ubuntu:24.04);LABEL maintainer="…" 取代已淘汰的 MAINTAINER。
  • RUN 在建置時執行指令、每個 RUN 產生一層;apt-get update && apt-get install -y … 寫在同一行,只裝需要的套件。
  • COPY 的來源相對於建置目錄;網頁要放到 nginx 會讀的路徑(Ubuntu 安裝的是 /var/www/html/,nginx 映像檔是 /usr/share/nginx/html/)。
  • CMD 建議用 exec 形式 ["程式", "參數"],只有最後一個有效,執行時加上指令會取代它;容器的主程式要留在前景,例如 nginx -g "daemon off;"。
  • EXPOSE 只是文件,真正對應埠號要在 docker container run 時用 -p 或 -P。
這一課完成了!
Next lesson:建立、測試與發布映像檔
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。