在上一篇我們完成了建立前端 Vue App、撰寫一些簡單的 API 並讓 MongoDB 的資料在容器停止或刪除後也能被保留下來,這篇我們來使用 docker-compose 整合我們的指令。
什麼是 docker-compose?
docker-compose 是一個用來跑多容器 Docker 應用程式的工具,藉由定義一個 .yml
檔,我們可以使用非常簡潔的指令告訴 Docker 如何建構映像檔以及啟動容器。
首先,我們可以在專案根目錄下新增 docker-compose.yml
:
version: "3.9"
services:
# mongodb:
# backend:
# frontend:
version
讓我們能為 compose 檔案設定版本,我們定義為目前最新的 3.9
。services
代表我們會使用的服務,到目前為止我們需要的分別有資料庫、後端和前端,這邊就分別使用 mongodb
, backend
, frontend
來命名。
MongoDB
我們回頭看一下我們啟動資料庫的指令:
docker run --name mevn-mongo -d --rm -v data:/data/db --network mevn-network -e MONGO_INITDB_ROOT_USERNAME=mongoadmin -e MONGO_INITDB_ROOT_PASSWORD=secret mongo
這邊我們是基於 mongo
這個映像檔,將其命名為 mevn-mongo
;使用 detach mode
讓它不佔用終端機;使用 --rm
讓容器停止時自動移除;使用 volume 將容器的 /data/db
映射到宿主機並交由 Docker 管理的 data
路徑;使用 mevn-network
作為 network
讓它能和後端溝通並定義了兩個環境變數。轉換為 compose
的設定就會是:
version: "3.9"
services:
mongodb:
image: 'mongo'
volumes:
- data:/data/db
env_file:
- ./env/mongodb.env
volumes:
data:
因為我們使用官方的映像檔,因此我們指定了 image
為 mongo
;volumes
其實就是把 -v data:/data/db
傳承過來;env_file
則是環境變數檔案,這邊可以在專案目錄新增一個 env
資料夾,並在內部新增一個 mongodb.env
:
MONGO_INITDB_ROOT_USERNAME=mongoadmin
MONGO_INITDB_ROOT_PASSWORD=secret
Docker 會去找到這個路徑並套用裡面的環境變數。我們不須特別指定 --rm
, 在我們使用 docker-compose down
停止服務時,Docker 會直接將容器移除;-d
則是在啟用服務時附加。Docker 也會對 docker-compose
內部的 services
建立一個可互通的 network
,因此我們也不需要特別去設定; 最下方的 volumes
則是告訴 Docker:我們使用了 data
這個路徑,請為我們建立對應的 volume
。
backend
接著我們看一下後端的部份。首先,後端的容器使用的映像檔不是來自官方,而是我們自建的。因此我們會需要告訴 Docker:請基於我指定的 Dockerfile 作為容器的映像檔。
接著我們回頭看一下後端的啟用指令:
docker run --rm --name mevn-backend-app -v "$(pwd)":/app -v /app/node_modules -p 3000:3000 --network mevn-network mevn-backend
這邊就從 volumes
開始。我們使用綁定掛載將後端的路徑綁定到容器的 /app
路徑中,並將 /app/node_modules
映射到某個由 Docker 管理的路徑,並將容器中的 3000
埠號映射到宿主機的 3000
埠號。設定檔看起來會像是:
backend:
# Dockerfile 位址
build: ./backend
ports:
- "3000:3000"
volumes:
- ./backend:/app
- /app/node_modules
env_file:
- ./env/backend.env
# 建立依賴關係,讓 depends_on 的 image 先於自己啟動
depends_on:
- mongodb
在 build 這邊我們指定了 ./backend
,這會讓 Docker
到 ./backend
資料夾中找到我們先前定義好的 Dockerfile
並基於它建立映像檔;ports
為我們指定映射的 3000
埠號;volumes
這邊分別定義了綁定掛載和 /app/node_modules
。由於我們的路徑就是 ./backend
,可以直接取代到實際是完整路徑的 pwd
;我們在這邊也可以補一個上一篇沒說到的:將資料庫連線的帳密改寫進環境變數,在 env
資料夾中新增 backend.env
:
MONGODB_USER=mongoadmin
MONGODB_PASSWORD=secret
接著將 ./backend/server.js
中原先寫死的資料改為:
mongoose.connect(`mongodb://${process.env.MONGODB_USER}:${process.env.MONGODB_PASSWORD}@mongodb:27017/mevn-cats?authSource=admin`, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
這邊 mevn-mongo:27017
改寫成了mongodb:27017
,對應到我們新的 service
名稱;depends_on
會告訴 Docker:請讓 mongodb
先啟動。
此時的檔案會像是:
version: "3.9"
services:
mongodb:
image: 'mongo'
volumes:
- data:/data/db
env_file:
- ./env/mongodb.env
backend:
# Dockerfile 位址
build: ./backend
ports:
- "3000:3000"
volumes:
- ./backend:/app
- /app/node_modules
env_file:
- ./env/backend.env
# 建立依賴關係,讓 depends_on 的 image 先於自己啟動
depends_on:
- mongodb
# frontend
volumes:
data:
這時我們可以在專案根目錄試著啟動(請先確定容器已經是關閉狀態):
docker-compose up -d
這一步會告訴 Docker:請在相同目錄下找到 docker-compose.yml
,並依據它的指示啟用我的服務。
這時在 http://localhost:3000/kittens 應該就能看到:
frontend
前端的部份,我們一樣先看看指令:
docker run --rm --name mevn-frontend-app -v "$(pwd)":/app -v /app/node_modules -p 5173:5173 mevn-frontend
這邊沒有什麼新的語法,我們直接看看 docker-compose.yml
的設定:
frontend:
build: ./frontend
ports:
- "5173:5173"
volumes:
- ./frontend:/app
- /app/node_modules
depends_on:
- backend
這時我們可以關閉並重啟服務:
# 關閉服務
docker-compose down# 啟動服務
docker-compose up
API 應該要可以正常串接外,當我們調整前端內容,畫面也會響應改變:
當我們調整後端的 API,前端在串接時也應該會發生問題:
app.post("/kittens", async (req, res) => {
return res.status(500).json({ message: '維護中' });
//...
到這裡,一個用於開發的 mevn-stack 就大功告成了!沒錯,距離要讓專案能上線,我們還需要拆分 production 環境。
關於使用 docker-compose
建立 mevn-stack 的筆記就先紀錄到這,後面(也許是 2022/11 或 2022/12)預計會再補上拆分 production,並會弄個網域並找一個平台(GCP 或 AWS,看我的帳號在哪邊的免費額度大)上線,我把專案的原始碼放在了這邊,後續隨著筆記的更新也會同步更新原始碼。
References: