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 nginx

docker 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 對應到 container 80

打開瀏覽器前往 http://localhost:9090,會看到 Nginx 的歡迎頁面。

Step 4:用 docker ps 檢查 container 狀態

這一步只看正在執行的 container:

docker ps

docker 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

先看三個重點:

欄位說明
STATUSUp 代表正在執行;Exited 代表已停止
PORTS0.0.0.0:9090->80/tcp 代表主機 9090 對到 container 80
NAMES這個 container 的名字,後面會用 nginx-life 操作它

如果要看所有 container,包含已停止的,再另外輸入:

docker ps -a

Step 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 通常沒有安裝 vimnano,所以課堂示範先用 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-life

restart 是同一個 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:rmrmi 是兩件事

docker rm 刪 container:

docker rm -f nginx-life

docker rmi 刪 image:

docker rmi nginx

如果還有 container 使用 nginx image,Docker 可能會拒絕刪除 image。要先刪 container,再刪 image。

課堂提醒:後面 Volume 練習還會用到 nginx,實際上課時可以先不要刪掉 image。這裡先讓學生知道 rmrmi 的操作對象不同。

生命週期指令總整理

指令操作對象用途
docker pull nginxImage從 Docker Hub 下載 image
docker imagesImage查看本機有哪些 image
docker run ... nginxImage -> Container用 image 建立並啟動 container
docker psContainer查看正在執行的 container
docker ps -aContainer查看所有 container,包含已停止的;-a 是 all
docker stop nginx-lifeContainer停止執行中的 container
docker start nginx-lifeContainer啟動已停止的 container
docker restart nginx-lifeContainer重新啟動 container
docker rm nginx-lifeContainer刪除已停止的 container
docker rm -f nginx-lifeContainer強制刪除 container
docker rmi nginxImage刪除 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 VolumeVolume 名稱Docker 管理刪掉 container 後,再掛同一個 volume 還看得到資料

先建立一個對照:沒有 Volume,改了會消失

如果你直接進 container 改首頁:

docker exec -it nginx-life sh
cd /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/htmlNginx 讀取網頁的資料夾

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 sh
cd /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 sh
cd /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 sh
cd /usr/share/nginx/html
cat index.html
echo '<h1>Try write</h1>' > index.html

cat 可以讀,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 VolumeVolume 名稱希望資料由 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 prune

prune 會刪掉所有「沒有被 container 使用」的 volume,正式環境不要隨便下。

Volume 總整理

技術指令形狀學生要記住的重點
Bind Mount-v "主機路徑:container路徑"主機和 container 看到同一份檔案
Read-only Bind Mount-v "主機路徑:container路徑:ro"container 只能讀,不能改主機檔案
Named Volume-v volume名稱:container路徑資料由 Docker 管理,container 重建後仍保留
Volume Inspectdocker 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.py

requirements.txt

flask==3.0.3

app.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 bash
pwd
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 觀念用到真正的程式碼開發流程。

0%