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)