團隊最近要使用 GitLab
CI/CD
功能了,DevOps 介紹完之後馬上就意識到有個很久的問題必須要解決,之前很菜的時候以及開發流程還沒有很完整的時候還可以手動來決定正式環境還是測試環境 docker 再打包就好,現在需要透過 Dockerfile
產生編譯結果以及透過文件指令來區分兩者的差別。
目標:
- 需要修改的項目越少越好。
- 最好的狀況是透過設定好的文件和指令互相作用產生不同的編譯結果。
主要步驟:
- 在 Dockerfile 產生編譯結果
- 透過打包指令產生正式環境或測試環境
菜雞流程
- 手動執行
npm run build
或npm run dev
- 打包成
image
docker build -t tag_name .
Dockerfile
單純只複製dist
內容
FROM node:lts-alpine
RUN npm install -g http-server
RUN mkdir dist
COPY ./dist ./dist
EXPOSE 8080
CMD [ "http-server", "dist" ]
這些動作我原本都是透過編輯 sh 檔來完成,不想要正式環境更新就把指令註解起來,sh maker.sh
系統就會做完所有事。在 Dockerfile 產生編譯結果
很多文章分享的大概會是這樣子的 Dockerfile 內容
FROM node:lts-alpine
RUN npm install -g http-server
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
RUN npm run build
EXPOSE 80
CMD [ "http-server", "dist" ]
如果以官方範例模板來說這段其實沒問題,因為檔案夠小還包得住,但是當專案已經安裝了很多套件或是有點規模時就包不住了,因為 CPOY . .
指令,這個指令是把根目錄的所有東西複製到 docker 環境裡,然而 node_modules
也一起複製進去了啊啊啊。
神奇的是即便我寫了 .dockerignore
忽略 node_modules
仍然會失敗(好像和內存有關??),所以我只好把 npm run build
的動作從 Dockerfile
分離,就產生了前一段的菜雞流程。
我把這個困擾我很久的問題描述給 DevOps,他跟我說 Dockerfile
在打包的時候可以多段建構,每一段的建構環境都是乾淨的而且可以互相指向,並給我他使用的 Dockerfile
給我當參考。
修改運行成功的結果 Dockerfile :
# Builder
FROM node:14.19 as builder
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
# Distribution
FROM node:lts-alpine
RUN npm install -g http-server
RUN mkdir dist
COPY --from=builder /app/dist ./dist
EXPOSE 8080
CMD [ "http-server", "dist" ]
第一段是在 Docker 進行編譯 npm run build
,第二段是只將編譯好的結果包起來。
切分建構階段的關鍵是 FROM
以及 as
指令,我們可以將第一段建構環境使用 as
命名,讓第二段建構可以透過 --from=builder
指向到第一段環境 COPY
出 dist
內容,docker 會依照最後建構階段的內容打包成 image
,並且容量也和菜雞流程包出來的容量一樣小。
要注意的是如果 FROM
一樣的話,那 docker 會自動認為那是同個環境,像是如果我把第二階段改成 FROM node:14.19
與第一段相同,那打包出來的容量會超大 XDDD,因為第一段執行過 npm install
產生的 node_modules
也一起包進去了。
透過打包指令產生正式環境或測試環境
由於前後分離的關係,前端程式碼大多已經是編譯好的結果,不像後端程式碼可以隨時修改環境變數產生變化,也就是所謂的靜態檔案,搜尋了很多如何讓前端程式碼根據指令來替換環境變數,第一時間看到的大多是透過 shell
指令來修改關鍵字,但是我覺得那樣太暴力了,應該有更好的方法。
理想的狀況是在 docker build
的時候修改 Dockerfile
,從 npm run build
改為 npm run dev
指令,但是 Dockerfile
所提供的指令 ENV
ARG
偏向修改環境變數,但環境變數已經寫好在專案中的 .env
了,所以不符合我的需求。
這問題困擾我很久,弄得我好焦慮 XD,突然間想到 不然輸入 docker build --help
看有什麼東西好了。
看到 Options
有段描述
-f, --file string Name of the Dockerfile (Default is 'PATH/Dockerfile')
原來 Dockerfile
不一定要叫 Dockerfile
啊 XDDD,也可以下指令讓 docker 去執行指定檔名,這樣我就可以寫好兩個 Dockerfile
然後下 docker build
指令時就可以分別使用不同的 Dockerfile
產生不同的前端環境。
我在根目錄寫了兩份 Dockerfile
分別是 Dockerfile_dev
Dockerfile_pro
,差別只有 npm run dev
和 npm run build
當我要產生測試環境時指令為:
docker build -t tag_name_dev -f ./Dockerfile_dev .
正式環境時指令為:
docker build -t tag_name_pro -f ./Dockerfile_pro .
這樣 docker 就可以根據我的指令產生不同編譯結果以及不同情境的 image,也達成了 目標 2. 透過設定好的文件和指令互相作用產生不同的編譯結果
。
檔名
上一段我把 Dockerfile
檔名改為 Dockerfile_dev
這樣在 VS Code 會失去主題,有夠醜XDD,改成 dev.Dockerfile
就會吃到主題,
但是 docker 相關的檔案被四散排列有點不好找,我再把檔名改成 .dDev.Dockerfile
,
docker 圖案排在一起看起來舒服多了XD,而且指令也能正常發動。
docker build -t tag_name_dev -f ./.dDev.Dockerfile .
結語
這次解決了一直以來的問題,更完善了團隊前端 docker 打包流程,現階段我所理解的前端打包部分就到這裡了,如果有新發現再回來修改~