Docker 教學 第 2 堂:容器生命週期、Nginx Volume 與 Flask 練習
2-1 容器生命週期與管理指令(約 50 分鐘)
容器基本操作概念
上一堂已經知道 Docker 會用 image 建立 container。這一節不要先背指令,先用 nginx 跑一次完整流程:
Registry(Docker Hub)
↓ docker pull
Image(nginx)
↓ docker run
Container(nginx-life)
↓ docker stop / docker start / docker restart
Running / Exited
↓ docker rm
Container 被刪除
↓ docker rmi
Image 被刪除這裡要先建立一個很重要的觀念:
- Image 是範本:像安裝檔或模板,本身不會因為容器裡的操作被改掉。
- Container 是執行個體:從 image 建立出來,可以啟動、停止、刪除。
- Container 預設是可拋棄的:你在 container 裡改的東西,如果沒有用 Volume 或重新 build 成 image,刪掉 container 後就會消失。這也是常說的「容器本身盡量保持無狀態(stateless)」。
今天先用 nginx 示範這件事,因為它一跑起來就有網頁可以看,學生會比較有感。
Step 1:先下載 nginx image
這一步只做一件事:把 nginx image 下載到本機。
docker pull nginxdocker pull 是從 Docker Hub 下載 image。其實 docker run nginx 在本機找不到 image 時也會自動下載,但課堂上先手動 pull,學生比較容易看懂「先有 image,才用 image 建 container」。
Step 2:確認本機 image 清單
不要急著接 run,先確認本機真的有 nginx image。
docker images你會看到類似:
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest xxxxxxxxxxxx 2 weeks ago 192MB注意:現在只有 image,還沒有 container。
Step 3:用 nginx image 啟動網站
這一步只做 run:
docker run -d --name nginx-life -p 9090:80 nginx這行指令的意思是:用 nginx image 建立一個叫做 nginx-life 的 container,並把主機的 9090 port 對應到 container 裡的 80 port。
nginx是 image 名稱nginx-life是 container 名稱-d:背景執行--name nginx-life:幫 container 取一個好記的名字-p 9090:80:主機9090對應到 container80
打開瀏覽器前往 http://localhost:9090,會看到 Nginx 的歡迎頁面。
Step 4:用 docker ps 檢查 container 狀態
這一步只看正在執行的 container:
docker psdocker ps 輸出範例:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9f89a41aca7d nginx "nginx -g..." 6 seconds ago Up 5 seconds 0.0.0.0:9090->80/tcp nginx-life先看三個重點:
| 欄位 | 說明 |
|---|---|
STATUS | Up 代表正在執行;Exited 代表已停止 |
PORTS | 0.0.0.0:9090->80/tcp 代表主機 9090 對到 container 80 |
NAMES | 這個 container 的名字,後面會用 nginx-life 操作它 |
如果要看所有 container,包含已停止的,再另外輸入:
docker ps -aStep 5:進入 nginx container 看首頁檔案
這一步示範「進入正在跑的 container」:
docker exec -it nginx-life sh進入 container 後,再一行一行輸入:
cd /usr/share/nginx/html
ls
cat index.html/usr/share/nginx/html/index.html 就是 Nginx 預設歡迎頁的位置。
docker exec的意思是在已經跑起來的 container 裡再開一個指令。Debug、看設定檔、確認檔案是否存在時都很常用。
Step 6:在 container 裡修改 index.html
官方 Nginx image 通常沒有安裝 vim、nano,所以課堂示範先用 echo 改檔案:
echo '<h1>Hello from container</h1>' > index.html
cat index.html
exit回到瀏覽器重新整理 http://localhost:9090,你會看到頁面變了。這裡不需要重啟 Nginx,因為 HTML 是靜態檔案,Nginx 每次收到請求都會重新讀檔案。
但是這個修改只存在於 nginx-life 這個 container 裡,不代表 nginx image 被改掉了。
Step 7:停止、啟動與重新啟動
停止 container:
docker stop nginx-life這時候 http://localhost:9090 會連不到。再看正在執行的 container:
docker ps你會發現 nginx-life 不見了,因為 docker ps 預設只看「正在執行」的 container。要看已停止的 container,再輸入:
docker ps -a把同一個 container 啟動回來:
docker start nginx-life重新整理瀏覽器,剛剛改過的 index.html 還在,因為這是同一個 container。
重新啟動同一個 container:
docker restart nginx-liferestart 是同一個 container 重開一次,不等於刪掉重建。
Step 8:刪掉 container,再用 image 重建
現在刪掉剛剛那個 container:
docker rm -f nginx-life確認 container 被刪掉:
docker ps -a接著用同一個 image 重新建立一個同名 container:
docker run -d --name nginx-life -p 9090:80 nginx再打開 http://localhost:9090,你會發現剛剛改過的 HTML 不見了,又回到 Nginx 預設歡迎頁。
這就是這堂課最重要的重點:
Container 可以停止、刪除、重建。Container 裡的修改不是長期保存資料的地方。
如果資料要留下來,要放到 container 外面,例如後面會講的 Volume / Bind Mount;如果環境要固定下來,就要寫 Dockerfile 重新 build image。
Step 9:rm 和 rmi 是兩件事
docker rm 刪 container:
docker rm -f nginx-lifedocker rmi 刪 image:
docker rmi nginx如果還有 container 使用 nginx image,Docker 可能會拒絕刪除 image。要先刪 container,再刪 image。
課堂提醒:後面 Volume 練習還會用到
nginx,實際上課時可以先不要刪掉 image。這裡先讓學生知道rm和rmi的操作對象不同。
生命週期指令總整理
| 指令 | 操作對象 | 用途 |
|---|---|---|
docker pull nginx | Image | 從 Docker Hub 下載 image |
docker images | Image | 查看本機有哪些 image |
docker run ... nginx | Image -> Container | 用 image 建立並啟動 container |
docker ps | Container | 查看正在執行的 container |
docker ps -a | Container | 查看所有 container,包含已停止的;-a 是 all |
docker stop nginx-life | Container | 停止執行中的 container |
docker start nginx-life | Container | 啟動已停止的 container |
docker restart nginx-life | Container | 重新啟動 container |
docker rm nginx-life | Container | 刪除已停止的 container |
docker rm -f nginx-life | Container | 強制刪除 container |
docker rmi nginx | Image | 刪除 image |
用完即丟的容器:--rm
如果你只是想臨時進一個容器測試一下,用完就不需要了,可以加上 --rm:
# 有 --rm:exit 離開後容器自動消失,不留垃圾
docker run -it --rm ubuntu bash
# 沒有 --rm:停止後容器還在,要手動 docker rm 才會刪掉
docker run -it ubuntu bash沒有 --rm | 有 --rm | |
|---|---|---|
| 停止後容器還在嗎 | 在(docker ps -a 看得到) | 自動刪除 |
| 適合場景 | 長期跑的服務(Web、DB) | 臨時測試、試指令、看看就走 |
--rm可以避免忘記清理而累積一堆已停止的容器。養成好習慣:臨時用的容器都加--rm。
2-2 Volume 基礎:用 Nginx 學 Bind Mount(約 50 分鐘)
為什麼接著講 Volume?
剛剛已經看過:container 可以停止、刪除、重建。這代表 container 自己的檔案系統很適合「用完可丟」,但不適合拿來保存資料。
這一節全程用 nginx,不要換來換去。因為 Nginx 的網頁目錄很固定:
/usr/share/nginx/html只要把不同來源掛到這個路徑,就能很清楚看到 Volume 的效果。
先分清楚:Mounted Volume vs Named Volume
很多同學會口語說「mounted volume」。這一堂課先把它對應到 Docker 的 Bind Mount:你自己指定主機資料夾,掛進 container 裡。
Mounted Volume(Bind Mount)範例:
docker run -d --name nginx-bind -p 8080:80 -v "C:\docker-lab\nginx-site:/usr/share/nginx/html" nginx- 左邊
C:\docker-lab\nginx-site:主機上的資料夾 - 右邊
/usr/share/nginx/html:Nginx 在 container 裡讀網頁的資料夾 - 適合:教學展示、開發時直接用編輯器改
index.html
Named Volume 範例:
docker volume create nginx-html
docker run -d --name nginx-volume -p 8082:80 -v nginx-html:/usr/share/nginx/html nginx- 左邊
nginx-html:Docker 管理的 volume 名稱 - 右邊
/usr/share/nginx/html:Nginx 在 container 裡讀網頁的資料夾 - 適合:希望資料跟 container 分開保存,但不想指定主機路徑
簡單記:
| 類型 | -v 左邊 | 誰管理資料 | 這堂課用途 |
|---|---|---|---|
| Mounted Volume(Bind Mount) | 主機路徑 | 你自己管理 | 直接修改主機 index.html,瀏覽器重新整理就變 |
| Named Volume | Volume 名稱 | Docker 管理 | 刪掉 container 後,再掛同一個 volume 還看得到資料 |
先建立一個對照:沒有 Volume,改了會消失
如果你直接進 container 改首頁:
docker exec -it nginx-life shcd /usr/share/nginx/html
echo '<h1>No volume</h1>' > index.html
exit瀏覽器重新整理會看到畫面改變。但是刪掉 container、再用同一個 image 重建:
docker rm -f nginx-life
docker run -d --name nginx-life -p 9090:80 nginx首頁會回到 Nginx 預設頁。這就是 Volume 要解決的問題:資料不要只放在 container 裡。
Bind Mount 的核心語法
Bind Mount 是把「主機上的資料夾」掛到「container 裡的資料夾」。
-v "主機路徑:container路徑"等一下要用的完整版本是:
-v "C:\docker-lab\nginx-site:/usr/share/nginx/html"請學生先抓住一件事:冒號左邊是主機,右邊是 container。
| 位置 | 這堂課使用的路徑 | 意思 |
|---|---|---|
| 主機 | C:\docker-lab\nginx-site | 你用記事本或編輯器修改的資料夾 |
| Container | /usr/share/nginx/html | Nginx 讀取網頁的資料夾 |
Step 1:建立本機網站資料夾
mkdir C:\docker-lab\nginx-site
notepad C:\docker-lab\nginx-site\index.html先放入第一版 HTML:
<h1>Hello Nginx Volume!</h1>
<p>這是第一個版本。</p>這一步還沒有啟動 container,只是在主機準備資料。
Step 2:用 Bind Mount 啟動 Nginx
docker run -d --name nginx-bind -p 8080:80 -v "C:\docker-lab\nginx-site:/usr/share/nginx/html" nginx打開瀏覽器:
http://localhost:8080你會看到主機 index.html 的內容。這時候 Nginx 不是讀 image 內建的歡迎頁,而是讀你掛進去的主機資料夾。
Step 3:修改主機檔案,瀏覽器直接變
用記事本再打開:
notepad C:\docker-lab\nginx-site\index.html改成第二版:
<h1>Hello Nginx Volume!</h1>
<p>這是修改後的第二個版本。</p>
<p>我在主機改檔案,nginx container 會立刻讀到。</p>回到瀏覽器重新整理 http://localhost:8080。不需要 docker restart,也不需要重新 build image。
原因是:Nginx 每次收到請求會讀 /usr/share/nginx/html/index.html,而這個路徑現在其實指向主機資料夾。
Step 4:進 container 確認掛載結果
docker exec -it nginx-bind shcd /usr/share/nginx/html
ls
cat index.html
exit你會看到的內容,會和主機 C:\docker-lab\nginx-site\index.html 一樣。
Step 5:反過來,在 container 裡新增檔案
Bind Mount 是同一份檔案系統,所以也可以反過來測試:
docker exec -it nginx-bind shcd /usr/share/nginx/html
echo '<h1>Created in container</h1>' > test.html
cat test.html
exit打開瀏覽器:
http://localhost:8080/test.html同時去主機資料夾 C:\docker-lab\nginx-site 看,會發現 test.html 也出現了。
Bind Mount 重點:主機和 container 看到的是同一份資料。改任何一邊,另一邊都會看到。
Step 6:唯讀掛載 :ro
有時候你只希望 container 讀取資料,不希望它改主機檔案。這時候在最後加上 :ro:
docker rm -f nginx-bind
docker run -d --name nginx-readonly -p 8081:80 -v "C:\docker-lab\nginx-site:/usr/share/nginx/html:ro" nginx進 container 測試:
docker exec -it nginx-readonly shcd /usr/share/nginx/html
cat index.html
echo '<h1>Try write</h1>' > index.htmlcat 可以讀,echo 寫入會失敗。這就是 read-only 掛載。
適合場景:
- 靜態網站只想讓 container 讀
- 設定檔不想被 container 改掉
- 課堂展示不希望學生誤改主機資料
Bind Mount 常見卡關點
| 問題 | 檢查方式 |
|---|---|
| 網頁變成 403 或空白 | 確認主機資料夾裡真的有 index.html |
| 路徑有空白或特殊字元 | Windows 路徑用雙引號包起來 |
| 改檔案後沒變 | 確認你改的是掛載的主機資料夾,不是別的資料夾 |
container 進不去 bash | 改用 sh,nginx 這類 image 通常都有 sh |
2-3 Volume 進階:用 Nginx 學 Named Volume(約 40 分鐘)
Mounted Volume(Bind Mount)和 Named Volume 差在哪?
前面 Bind Mount 的資料在你指定的主機路徑:
-v "C:\docker-lab\nginx-site:/usr/share/nginx/html"Named Volume 則是給資料一個名字,實際位置交給 Docker 管理:
-v nginx-html:/usr/share/nginx/html| 類型 | 左邊是什麼 | 適合情境 |
|---|---|---|
| Bind Mount | 主機路徑 | 開發、示範、直接用編輯器改檔案 |
| Named Volume | Volume 名稱 | 希望資料由 Docker 管理,不綁死主機路徑 |
Volume 管理指令
docker volume ls
docker volume create nginx-html
docker volume inspect nginx-html
docker volume rm nginx-html
docker volume prune先不要急著背,等一下會照順序用到。
Step 1:建立 Named Volume
docker volume create nginx-html
docker volume ls你會看到 nginx-html 出現在 volume 清單裡。
Step 2:用 Named Volume 啟動 Nginx
docker run -d --name nginx-volume -p 8082:80 -v nginx-html:/usr/share/nginx/html nginx這次 -v 左邊不是 Windows 路徑,而是一個 volume 名稱:nginx-html。
Step 3:把首頁寫進 Named Volume
docker exec nginx-volume sh -c "echo '<h1>Hello Named Volume</h1>' > /usr/share/nginx/html/index.html"打開:
http://localhost:8082你會看到 Hello Named Volume。
Step 4:刪掉 container,再掛同一個 volume
刪掉 container:
docker rm -f nginx-volume用同一個 volume 重建:
docker run -d --name nginx-volume -p 8082:80 -v nginx-html:/usr/share/nginx/html nginx重新整理 http://localhost:8082,剛剛寫進去的首頁仍然存在。
這就是 Named Volume 的重點:container 可以重建,volume 還在。
Step 5:查看 Docker 管理的位置
docker volume inspect nginx-html你會看到類似 Mountpoint 的資訊。這是 Docker 在本機管理 volume 的實際位置。課堂上只要讓學生知道有這個位置,不需要手動進去改。
Step 6:兩個 Nginx 共用同一個 Named Volume
可以讓兩個 Nginx container 讀同一份 nginx-html:
docker run -d --name nginx-vol-a -p 8083:80 -v nginx-html:/usr/share/nginx/html:ro nginx
docker run -d --name nginx-vol-b -p 8084:80 -v nginx-html:/usr/share/nginx/html:ro nginx打開:
http://localhost:8083
http://localhost:8084兩個 port 會看到同一份 HTML。這裡使用 :ro,是因為多個 container 共用同一份資料時,先用唯讀比較安全。
Step 7:清理
先刪 container:
docker rm -f nginx-bind nginx-readonly nginx-volume nginx-vol-a nginx-vol-b再刪 volume:
docker volume rm nginx-html如果只是課堂後清理所有未使用的 volume,可以用:
docker volume pruneprune 會刪掉所有「沒有被 container 使用」的 volume,正式環境不要隨便下。
Volume 總整理
| 技術 | 指令形狀 | 學生要記住的重點 |
|---|---|---|
| Bind Mount | -v "主機路徑:container路徑" | 主機和 container 看到同一份檔案 |
| Read-only Bind Mount | -v "主機路徑:container路徑:ro" | container 只能讀,不能改主機檔案 |
| Named Volume | -v volume名稱:container路徑 | 資料由 Docker 管理,container 重建後仍保留 |
| Volume Inspect | docker volume inspect 名稱 | 查看 Docker 管理 volume 的資訊 |
這一章的結論:
Container 可以刪、可以重建;要留下的資料,請放到 Volume。
2-4 Flask 練習:用 Bind Mount 跑 Python Web App(約 40 分鐘)
為什麼 nginx 後面接 Flask?
剛剛用 nginx 學會了兩件事:
-v 主機路徑:container路徑會讓主機和 container 看到同一份檔案。- container 可以刪掉重建,資料要放在 container 外面。
接著用 Flask 練習同一個觀念,但這次掛進去的不是靜態 HTML,而是一個 Python 專案。這樣學生會看到:Bind Mount 不只可以掛網頁檔,也可以掛程式碼。
這一段先不寫 Dockerfile,也不 build image。先用官方 python:3.11 image 當練習環境,把本機 Flask 專案掛進 container 裡跑。
Step 1:建立 Flask 專案資料夾
mkdir C:\docker-lab\flask-app
notepad C:\docker-lab\flask-app\requirements.txt
notepad C:\docker-lab\flask-app\app.pyrequirements.txt:
flask==3.0.3app.py:
from flask import Flask
app = Flask(__name__)
@app.get("/")
def index():
return "<h1>Hello Flask Volume!</h1><p>這是從 bind mount 掛進來的程式碼。</p>"
@app.get("/about")
def about():
return "<h1>About</h1><p>修改主機 app.py 後,重新啟動 Flask 就會看到新內容。</p>"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)先提醒學生兩個重點:
host="0.0.0.0":Flask 必須聽 container 的所有網路介面,主機瀏覽器才連得到。port=5000:Flask 在 container 裡跑 5000 port,等一下用-p 5000:5000對外開出來。
Step 2:用 Python container 掛載專案
docker run -it --rm --name flask-dev -p 5000:5000 -v "C:\docker-lab\flask-app:/app" -w /app python:3.11 bash這行指令和剛剛 nginx 的 Bind Mount 是同一個概念,只是 container 內路徑換成 /app。
| 參數 | 意思 |
|---|---|
--rm | 離開 container 後自動刪掉,適合開發練習 |
--name flask-dev | 幫這次練習的 container 取名 |
-p 5000:5000 | 主機 5000 對到 container 5000 |
-v "C:\docker-lab\flask-app:/app" | 把本機 Flask 專案掛到 container 的 /app |
-w /app | 進 container 後,工作目錄直接在 /app |
python:3.11 bash | 用 Python image 開一個 shell |
Step 3:安裝 Flask 並啟動
進入 container 後輸入:
pip install -r requirements.txt
python app.py打開瀏覽器:
http://localhost:5000
http://localhost:5000/about你會看到 Flask 回傳的頁面。這代表:
- 主機的
app.py已經掛進 container 的/app/app.py - Flask 在 container 裡跑起來
-p 5000:5000讓主機瀏覽器可以連進 container
Step 4:修改主機 app.py,重啟 Flask
先不要刪 container。直接在主機改檔案:
notepad C:\docker-lab\flask-app\app.py例如把首頁改成:
@app.get("/")
def index():
return "<h1>Hello Flask Again!</h1><p>我在主機改 app.py,container 讀到同一份檔案。</p>"回到正在跑 Flask 的終端機,按 Ctrl + C 停止 Flask,再重新啟動:
python app.py重新整理 http://localhost:5000,你會看到新內容。
這裡和 nginx 的差異要講清楚:
- nginx 讀靜態 HTML,改檔案後重新整理通常就會變。
- Flask 是正在跑的 Python 程式,程式碼已經被 process 載入;改
app.py後通常要重啟 Flask process,或改用 debug reload。
Step 5:從另一個終端機確認 /app
如果 Flask 還在跑,可以另開一個 PowerShell 檢查 container 裡看到的檔案:
docker exec -it flask-dev bashpwd
ls
cat app.py
exit你會看到 /app 裡的內容就是主機 C:\docker-lab\flask-app。
Step 6:清理 Flask 練習容器
回到 Flask 那個終端機,按 Ctrl + C 停止 Flask,再輸入:
exit因為一開始有加 --rm,離開後 container 會自動刪掉。確認一下:
docker ps -a通常不會再看到 flask-dev。
nginx 和 Flask 的學習差異
| 比較 | nginx 靜態網頁 | Flask Python App |
|---|---|---|
| 掛載內容 | HTML / CSS / 靜態檔 | Python 程式碼與 requirements |
| Container 內路徑 | /usr/share/nginx/html | /app |
| 對外 port | -p 8080:80 | -p 5000:5000 |
| 改主機檔案後 | 重新整理瀏覽器通常就變 | 通常要重啟 Flask process |
| 這堂課重點 | 看懂 Volume 同步 | 看懂開發時掛載程式碼 |
第二堂最後要讓學生能說出:
nginx 讓我們看懂 Volume;Flask 讓我們把同一個 Volume 觀念用到真正的程式碼開發流程。