# Docker 教學 第 2 堂：Flask 容器、容器管理與 Volume


## 2-1 建立 Flask Web 容器（約 1 小時）

### 容器基本操作概念

現在我們要把映像檔「跑起來」，變成容器。最常用的指令是 `docker run`。

**`docker run` 做了什麼？**
1. 檢查本機是否有指定的映像檔
2. 如果沒有，自動從 Docker Hub 下載
3. 用映像檔建立一個新的容器
4. 啟動容器

### 實作：啟動一個 Python 容器

```powershell
# 啟動一個 Python 容器，進入互動模式
docker run -it python:3.11 python
```

**參數說明：**
- `-i`（interactive）：保持標準輸入開啟
- `-t`（tty）：分配一個終端
- `-it`：這兩個通常一起用，讓你可以跟容器互動
- 最後的 `python`：進入容器後要執行的指令

現在你已經在容器裡的 Python 互動環境了！試試看：
```python
print(&#34;Hello from Docker!&#34;)
import sys
print(sys.version)
```

按 `Ctrl &#43; D` 或輸入 `exit()` 離開。

### 實作：用 Flask 建立一個 Web 應用容器

**步驟 1：在本機建立專案資料夾**

打開 PowerShell：
```powershell
mkdir C:\docker-lab\flask-app
cd C:\docker-lab\flask-app
```

**步驟 2：建立 Flask 應用程式**

用記事本或任何編輯器建立 `app.py`：
```python
from flask import Flask

app = Flask(__name__)

@app.route(&#39;/&#39;)
def hello():
    return &#39;&lt;h1&gt;Hello Docker!&lt;/h1&gt;&lt;p&gt;This is running inside a container.&lt;/p&gt;&#39;

@app.route(&#39;/about&#39;)
def about():
    return &#39;&lt;h1&gt;About&lt;/h1&gt;&lt;p&gt;This is a Flask app running in Docker.&lt;/p&gt;&#39;

if __name__ == &#39;__main__&#39;:
    app.run(host=&#39;0.0.0.0&#39;, port=5000)
```

**步驟 3：建立 requirements.txt**
```
flask
```

**步驟 4：啟動容器並執行 Flask**

```powershell
docker run -it -p 5000:5000 -v C:\docker-lab\flask-app:/app -w /app python:3.11 bash
```

**參數解釋（重點！）：**
- `-p 5000:5000`：端口映射，把容器的 5000 port 映射到主機的 5000 port
- `-v C:\docker-lab\flask-app:/app`：掛載目錄，把本機的資料夾映射到容器裡的 `/app`
- `-w /app`：設定工作目錄為 `/app`
- `bash`：進入 bash shell

&gt; **端口映射**是初學者最容易搞混的地方。用比喻解釋：容器就像一棟大樓，裡面有很多房間（port），你需要在大樓外面立一個指標牌（映射），告訴外面的人要走哪個入口才能到達對的房間。

**步驟 5：在容器裡安裝套件並啟動**
```bash
pip install -r requirements.txt
python app.py
```

**步驟 6：測試**
- 打開瀏覽器，前往 `http://localhost:5000`
- 你應該看到「Hello Docker!」

&gt; 試著修改 `app.py` 的回傳文字，然後重新啟動 Flask，體驗掛載目錄的方便——不需要重建容器就能更新程式碼。

---

## 2-2 容器管理指令大全（約 1 小時）

### 查看容器狀態

```powershell
# 查看執行中的容器
docker ps

# 查看所有容器（包含已停止的）
docker ps -a
```

`docker ps` 輸出範例：
```
CONTAINER ID   IMAGE   COMMAND         CREATED        STATUS        PORTS                  NAMES
9f89a41aca7d   nginx   &#34;nginx -g...&#34;   6 seconds ago  Up 5 seconds  0.0.0.0:8080-&gt;80/tcp   my-web
```

**各欄位說明：**

| 欄位 | 說明 |
|------|------|
| `CONTAINER ID` | 容器的唯一識別碼（就像身分證號） |
| `IMAGE` | 建立容器所用的映像檔 |
| `COMMAND` | 容器啟動時執行的命令（程式的入口點） |
| `CREATED` | 容器建立的時間（多久前開的） |
| `STATUS` | 目前狀態（Up 執行中、Exited 已停止、Restarting 重啟中...） |
| `PORTS` | 容器對外映射的端口（`0.0.0.0:8080-&gt;80` 表示主機 8080 對應容器 80） |
| `NAMES` | 容器的名字（方便人類記） |

### 停止、啟動與重新啟動

```powershell

# 停止容器
docker stop &lt;容器ID或名稱&gt;

# 啟動已停止的容器
docker start &lt;容器ID或名稱&gt;

# 重新啟動容器（等於 stop &#43; start）
docker restart &lt;容器ID或名稱&gt;
```

&gt; **`start` vs `restart` 的差異：**
&gt; - `docker start`：只能啟動「已停止」的容器。對正在執行中的容器輸入 `docker start` 不會有任何效果。
&gt; - `docker restart`：不管容器是執行中還是已停止，都會重新啟動。
&gt;
&gt; **什麼時候要用 `restart`？** 實務上當容器的應用程式需要維護或更新設定、或是服務出問題（例如線程佔滿、記憶體洩漏）時，用 `docker restart` 可以快速重啟服務。

### 刪除容器

```powershell

# 刪除容器（必須先停止才能刪）
docker rm &lt;容器ID或名稱&gt;

# 強制刪除執行中的容器（跳過停止步驟）
docker rm -f &lt;容器ID或名稱&gt;
```

&gt; **為什麼刪除容器之前需要先停止？**
&gt;
&gt; 執行中的容器裡可能有程式正在處理資料（例如資料庫正在寫入）。如果直接刪除，可能會造成資料損壞或遺失。所以 Docker 預設要求你先 `docker stop` 讓程式優雅地結束，再 `docker rm` 刪除。
&gt;
&gt; `docker rm -f` 則是強制刪除，不管容器在不在跑都直接砍。開發環境用很方便，但正式環境請盡量先 stop 再 rm。

### 查看 Log 與進入容器

```powershell

# 查看容器的 log
docker logs &lt;容器ID或名稱&gt;

# 進入一個執行中的容器
docker exec -it &lt;容器ID或名稱&gt; bash
```

&gt; **什麼情況會用 `docker exec`？**
&gt;
&gt; | 情境 | 範例指令 |
&gt; |------|---------|
&gt; | **Debug 除錯** — 程式出問題，進去看 log 檔或設定檔 | `docker exec -it my-app bash` 然後 `cat /app/error.log` |
&gt; | **確認部署結果** — 檢查檔案有沒有正確複製、套件裝好了沒 | `docker exec -it my-app ls /app` |
&gt; | **操作資料庫** — 直接進 MySQL 下 SQL 指令 | `docker exec -it my-mysql mysql -u root -p` |
&gt; | **臨時測試** — 進去改個設定檔快速驗證想法 | `docker exec -it my-app bash` 然後修改檔案 |
&gt; | **查看系統狀態** — 看記憶體、程序列表等 | `docker exec -it my-app top` |
&gt;
&gt; 簡單來說：任何時候你需要「進去容器裡面看看」或「在容器裡跑一個指令」的時候，就用 `docker exec`。

### 幫容器取名字

```powershell
docker run -d --name my-flask -p 5000:5000 -v C:\docker-lab\flask-app:/app -w /app python:3.11 bash -c &#34;pip install flask &amp;&amp; python app.py&#34;
```

新參數：
- `-d`（detach）：在背景執行
- `--name my-flask`：幫容器取一個好記的名字

### 用完即丟的容器：`--rm`

如果你只是想臨時進一個容器測試一下，用完就不需要了，可以加上 `--rm`：

```powershell
# 有 --rm：exit 離開後容器自動消失，不留垃圾
docker run -it --rm ubuntu bash

# 沒有 --rm：停止後容器還在，要手動 docker rm 才會刪掉
docker run -it ubuntu bash
```

| | 沒有 `--rm` | 有 `--rm` |
|---|---|---|
| 停止後容器還在嗎 | 在（`docker ps -a` 看得到） | 自動刪除 |
| 適合場景 | 長期跑的服務（Web、DB） | 臨時測試、試指令、看看就走 |

&gt; `--rm` 可以避免忘記清理而累積一堆已停止的容器。養成好習慣：臨時用的容器都加 `--rm`。

---

## 2-3 Volume 資料儲存（約 1 小時）

### 容器的資料會消失？

容器天生是「用完即丟」的設計。當你刪除一個容器時，裡面所有的檔案都會跟著消失。這在跑 Web 伺服器時沒問題（重建就好），但如果是資料庫，資料消失就完蛋了。

**Volume** 就是 Docker 用來解決這個問題的機制——把資料存在容器外面，容器刪掉了資料還在。

### 兩種掛載方式

Docker 的 `-v` 參數有兩種用法：

**1. Bind Mount（綁定掛載）：把主機的資料夾映射進容器**
```powershell
docker run -v &#34;C:\my-project:/app&#34; my-image
```
- 左邊是主機路徑，右邊是容器路徑
- 主機和容器看到的是**同一份檔案**，改了會同步
- 適合開發環境：改程式碼不用重建容器

**2. Named Volume（具名資料卷）：讓 Docker 管理資料**
```powershell
docker run -v my-data:/var/lib/mysql mysql:8.0
```
- 左邊是 Volume 名稱（不是路徑），右邊是容器路徑
- 資料存在 Docker 管理的位置，你不用管它放在哪
- 適合資料庫：安全、不會被意外修改

**兩者的比較：**

| | Bind Mount | Named Volume |
|---|---|---|
| 語法 | `-v &#34;C:\路徑:/容器路徑&#34;` | `-v 名稱:/容器路徑` |
| 資料存在哪 | 你指定的主機路徑 | Docker 自動管理 |
| 適合場景 | 開發時同步程式碼 | 資料庫等需要持久化的資料 |
| 可攜性 | 差（路徑綁死在主機） | 好（跟著 Docker 走） |

### Volume 管理指令

```powershell
# 查看所有 Volume
docker volume ls

# 建立一個 Volume
docker volume create my-data

# 查看 Volume 詳情
docker volume inspect my-data

# 刪除 Volume
docker volume rm my-data

# 刪除所有沒在使用的 Volume
docker volume prune
```

### 唯讀掛載

如果你只想讓容器讀取檔案、不允許修改，可以加上 `:ro`（read-only）：

```powershell
docker run -v &#34;C:\my-config:/app/config:ro&#34; my-image
```

這在掛載設定檔時很實用，避免容器內的程式意外修改到你的檔案。

### 實作：親眼看到 Volume 的同步效果

來實際體驗「改宿主機的檔案，容器裡面馬上跟著變」：

**步驟 1：建立測試資料夾和檔案**

```powershell
mkdir C:\docker-lab\volume-test
```

在 `C:\docker-lab\volume-test\` 下建立 `index.html`：

```html
&lt;h1&gt;Hello Volume!&lt;/h1&gt;
&lt;p&gt;這是第一個版本&lt;/p&gt;
```

**步驟 2：啟動 Nginx 容器，掛載這個資料夾**

```powershell
docker run -d --name volume-demo -p 8080:80 -v &#34;C:\docker-lab\volume-test:/usr/share/nginx/html&#34; nginx
```

**步驟 3：確認網頁正常顯示**

打開瀏覽器 `http://localhost:8080`，看到「Hello Volume! 這是第一個版本」。

**步驟 4：進容器確認檔案**

```powershell
docker exec -it volume-demo sh
```

```sh
cat /usr/share/nginx/html/index.html
# 會看到跟宿主機一模一樣的內容
exit
```

**步驟 5：在宿主機修改檔案**

用記事本打開 `C:\docker-lab\volume-test\index.html`，改成：

```html
&lt;h1&gt;Hello Volume!&lt;/h1&gt;
&lt;p&gt;這是修改後的第二個版本！&lt;/p&gt;
&lt;p&gt;我在宿主機改的，容器裡面會自動同步&lt;/p&gt;
```

**步驟 6：重新整理瀏覽器**

直接按 F5 重新整理 `http://localhost:8080`，馬上看到新內容！不需要重啟容器、不需要重新 build。

**步驟 7：反過來也行——在容器裡改，宿主機也會變**

```powershell
docker exec -it volume-demo sh
```

```sh
# 在容器裡面新增一個檔案
echo &#34;&lt;h1&gt;Created inside container!&lt;/h1&gt;&#34; &gt; /usr/share/nginx/html/test.html
exit
```

打開 `http://localhost:8080/test.html`，看到內容了。同時去宿主機的 `C:\docker-lab\volume-test\` 看，`test.html` 也出現了！

**步驟 8：清理**

```powershell
docker rm -f volume-demo
```

&gt; **重點整理**：Bind Mount 就是宿主機和容器「共用同一份檔案」，改任何一邊另一邊都會馬上看到。這就是開發時用 Volume 的最大好處——改程式碼不用重建容器。


---

> 作者: luk  
> URL: https://yoru-karu-blog-lalaluk-52581ac5e0cef170a3c8922c19182ecb6f7bd604.gitlab.io/posts/tutorial/docker/docker-session2-flask-volume/  

