---
# System prepended metadata

title: Docker tutorial -2

---

Docker tutorial -2
===
![](https://www.capdio.com/wp-content/uploads/2024/07/Docker-Logo.png)
**📌 本次內容：**
- 端口映射（Port mapping）
- 建立 Dockerfile，打包自己的image
- Docker compose 
- 實作一個本地的LLM（Ollama＋Open WebUI）
---

![](https://goodvibe.tw/wp-content/uploads/2023/12/basic-website-development-image-7-1024x453.png.webp)

## 端口映射（Port mapping）
在 Docker 中，容器內部運行的服務（例如 Web 伺服器、資料庫等）通常會綁定到**容器的內部端口**，但這個內部端口對外部世界是不可見的。
端口映射（Port mapping）是讓**容器內的端口**與**本機的端口**對接，這樣才能夠從主機或其他容器訪問容器內運行的服務。

:::info
端口映射（Port mapping）就像是把辦公室內線電話（容器端口）轉接到手機（本機端口），這樣外部才能撥打這支電話。
:::
### `-p` 參數

#### 語法格式
```
-p <host_port>:<container_port>
```
- `host_port`：宿主機上的端口
- `container_port`：容器內部的端口

#### 範例
1. **Web 伺服器運行**
假設要啟動一個運行 Web 伺服器的容器，並希望讓本機或其他設備能夠通過瀏覽器訪問它。

    ```
    $ docker run -d -p 8080:80 my_web_image
    ```
- `-p 8080:80`：將宿主機的端口 `8080` 映射到容器的端口 `80`
- 可以通過 `http://localhost:8080` 來訪問容器內部的 Web 伺服器
2. **Web + Database 運行**
也可以在啟動容器時，進行**多端口映射**，這樣就能同時訪問容器內部的多個服務

    ```
    $ docker run -d -p 8080:80 -p 3306:3306 my_database_web_image
    ```
- `8080` 端口訪問容器內部的 Web 伺服器
- `3306` 端口訪問容器內部的 MySQL 資料庫

### 端口數字

#### 容器內端口
1. 查看 image 官方說明
2. 自己創建 Dockerfile 時定義

#### 宿主機端口
可以自訂數字，但有幾個小規則：
1. `8080`, `8888`, `3000`, `5000` 等是開發常用的 port 👍🏻
2. 不要重複選到正在運行的 port
3. 避開常見系統服務的 port（如：`80`, `443`, `3306`...）
4. `1024` 以下的 port 會需要 sudo 權限
5. 可使用 `docker container ls` 檢查正在運行的容器端口映射情況

## Dockerfile

Dockerfile 是一個用 YAML 語法格式撰寫的純文字檔。裡面會透過多種「指令」來一步一步描述如何建置出一個 Image。
通常會創建在你本機中，專案的資料夾底下，如下結構圖。

```
myProject/
├── app.py
├── requirements.txt
└── Dockerfile
```
:::info
Docker 預設會找尋目錄中的 "Dockerfile" 來執行，所以這個檔案名稱是約定成俗的，通常不建議自行改名～ 
:::

### Dockerfile 常用創建指令

| 指令           | 說明   | 格式     |範例     |
|----------------|-------|-------|-------|
| `FROM`         | 設定要使用哪個基底 Image，所有 Dockerfile 都要有這個指令。        | `FROM <image>`, `FROM <image>:<tag>`                       |`FROM python:3.10`                       |
| `WORKDIR`      | 設定容器的工作目錄，後續的指令都會在這個目錄下執行。    | `WORKDIR <path>`                            |`WORKDIR /app`                            |
| `COPY`         | 複製本地檔案到容器內。                                    | `COPY <source path> <dist path>`|`COPY requirements.txt .`（`.`的意思是當前目錄）                                |
| `RUN`          | 在建構 Image 時執行的命令，常用來安裝軟體包或執行其他初始化操作。   | `RUN <command> && <command>`（一個`RUN` 就是開啟一個新的映像層，所以只會寫一個`RUN`，用`&&`來連接不同執行步驟）|`RUN apt-get update && apt-get install -y python3` |
| `CMD`          | 設定容器啟動後執行的命令，通常只會有一個 `CMD` 指令。       | `CMD [“command name”, argv1, argv2, …]` |`CMD ["python", "app.py"]`               |
| `EXPOSE`       | 指定容器內（服務器）的端口。      | `EXPOSE <port>`                            |`EXPOSE 8080`                            |
| `LABEL`        | 添加一些資訊，如：版本號、作者、描述等。               | `LABEL <key1>=<value1> <key2>=<value2> ` |`LABEL version="1.0" maintainer="Michael"` |

#### Dockerfile 範例
```
FROM python:3.10

WORKDIR /app

COPY requirements.txt .

RUN pip install -r requirements.txt

COPY . .

CMD ["python", "app.py"]
```

### 創建 Image

```
docker build -t <name>:<tag> <Dockerfile path>
```
- `-t`, `--tag`：指定 Image 的名稱與版本標籤。
- `<name>`：看自己想要為這個 Image 取什麼名字。
- `<tag>`：預設是`:latest`，開發測試中可以用 `:dev` 或 `:test`，穩定版本可以用 `:v1.0` 或`:stable` 等等。
- `<Dockerfile path>`：Dockerfile所在的目錄。通常會先進到該專案資料夾中，這時可以用 `.` 表示當前工作目錄。


#### 實作
```
$ docker build -t 0509_demo:test .

$ docker run -d -p 7860:7860 0509_demo:test

# 瀏覽器：http://localhost:7860
```

### 推上 Docker Hub
```
$ docker login

# 格式
$ docker tag <local-image-name> <dockerhub-username>/<image-name>:<tag>
$ docker tag 0509_demo jack555/0509_demo:dev

# 格式
$ docker push <dockerhub-username>/<image-name>:<tag>
$ docker push jack555/0509_demo:dev
```


## Docker compose
Docker Compose 是一個工具，它可以用一個 YAML 檔案，通常叫做 docker-compose.yml，來定義和管理多個 Container 的服務。
例如下圖：在專案資料夾中建立 docker-compose.yml，即可用一行指令，根據 docker-compose.yml 去同時啟動 Web 伺服器 + 後端 API + 資料庫。

```
myProject/
├── docker-compose.yml
├── frontend/
│   ├── Dockerfile
│   └── ...
│
├── backend/
│   ├── Dockerfile
│   ├── requirements.txt
│   └── ...
│
└── db/
    ├── init.sql
    └── ...
```
### docker-compose.yml 寫法
```
services:
  服務名稱:
    image: 映像檔名稱
    container_name: 自定義容器名稱
    ports: 
      - <主機 port>:<容器 port>
    volumes:
      - <主機 path>:<容器 path>
    environment:
      - 環境變數=值
    depends_on:
      - 其他服務名稱
    networks:
      - 自定義網路名稱
    restart: always / unless-stopped / no / on-failure
    command: 覆蓋預設執行指令
    

volumes:
  volume名稱:

networks:
  網路名稱:

```

### 實作：Ollama + Open WebUI

```
services:
  ollama:   # 服務 1
    image: ollama/ollama
    container_name: ollama
    ports:
      - "11434:11434"  # Ollama API port
    volumes:
      - ollama-data:/root/.ollama  # 儲存模型資料
    restart: unless-stopped

  open-webui:  # 服務 2
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    ports:
      - "3000:8080"  # 網頁前端 http://localhost:3000
    environment:
      - OLLAMA_API_BASE_URL=http://ollama:11434
    depends_on:
      - ollama
    restart: unless-stopped
    volumes:
      - openwebui-data:/app/backend/data  # 儲存 WebUI 設定資料

volumes:
  ollama-data:
  openwebui-data:

```
#### 運行
根據 docker-compose.yml 啟動所有服務。
```
$ docker compose up -d   # -d 不佔用終端機的方式在背景運行

# 瀏覽器：http://localhost:3000
```

#### 關閉
停掉所有服務，並刪除 Container（但不會刪掉 Image 和 Volume）。
```
$ docker compose down 
```
暫時停掉，但不刪除 Container。
```
$ docker compose stop
```

#### 重新啟動
啟動已存在的 Container，不會重新建構。
```
$ docker compose start
```

#### 選擇模型
```
$ docker exec -it ollama ollama pull <LLM>:<tag>

$ docker exec -it ollama ollama pull gemma3:1b
```
[官方 Ollama 模型 library](https://ollama.com/library)：去選擇你想要跑什麼模型


## References
1. [Docker 進階實戰：Volume、Port Mapping 與 Docker Compose 完整指南](https://realnewbie.com/basic-concent/architecture/docker-advanced-hands-on-guide-volume-port-mapping-and-docker-compose/)
2. [Docker 學習筆記 (四) — 如何撰寫Dockerfile](https://medium.com/%E4%B8%80%E5%80%8B%E5%B0%8F%E5%B0%8F%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E9%9A%A8%E6%89%8B%E7%AD%86%E8%A8%98/docker-%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-%E5%9B%9B-%E5%A6%82%E4%BD%95%E6%92%B0%E5%AF%ABdockerfile-2a209b485530)
3. [使用 Docker Compose 摻在一起做懶人包 ](https://ithelp.ithome.com.tw/articles/10243618)