今天將會應用前五天 build image 的技巧,來為以下框架的 hello world 寫 Dockerfile。
這幾個框架在台灣應該都很冷門,筆者主要是想示範:只要有 image 就有辦法初始化程式;只要有程式和 Dockerfile,讀者就可以建得出跟筆者一樣的環境與 server,這正是 Docker 實現 IaC 特性的方便之處。
以下示範四種框架的方法都大同小異,初始化程式是使用了應用 Container 裡提到的借用指令技巧來做;寫 Dockerfile 則是與 Laravel 建 image 做法一樣,一步一步寫出來。筆者會直接把結果放上來,但會說明哪個地方讓筆者撞牆很久。
Phoenix 使用 Elixir 語言撰寫,參考官網的 hello world 建置與執行指令如下:
mix archive.install hex phx_new 1.5.5
mix phx.new --no-ecto --no-webpack --app ironman phoenix
# 啟動 server
mix phx.server
mix
是 Elixir 內建的套件管理工具。
從這裡可以看得出,它框架初始化的做法與 Laravel 或 Node 類似:透過套件管理工具下載框架提供的工具,再用這個工具來初始化程式。因此可以直接使用 Elixir image,進入 container 直接執行上面兩個指令,即可產生 Phoenix 的初始化程式碼在 host 裡。
docker run --rm -it -v $PWD:/source -w /source elixir:1.10-alpine sh
FROM elixir:1.10-alpine
WORKDIR /usr/app/src
# 準備套件工具的設定
RUN set -xe && \
mix local.hex --force && \
mix local.rebar --force
# 安裝套件
COPY mix.* ./
RUN mix deps.get
# 原始碼與編譯
COPY . .
RUN mix compile
# 啟動 server 的指令
CMD ["mix", "phx.server"]
最後是 run image:
docker build -t=phoenix .
docker run --rm -it -p 4000:4000 phoenix
其他框架因為只是名稱和 port 不同,所以就不重覆此段範例了。
Phoenix 是編譯語言,所以會比 Laravel 多了編譯的過程。另外啟用 hex 與 rebar 套件在建 image 過程就有明顯的提示訊息,所以都很容易解決。
Amber 使用 Crystal 撰寫。先找到如何產生初始化程式的指令:
amber new --minimal amber
這裡會發現它跟 Phoenix 不一樣,有自己專屬的指令 amber
在處理初始化。而這個指令需要編譯,且需要 Crystal 的環境,有點麻煩。幸好 Amber 官方已經建好 Amber image 可以直接使用了。
docker run --rm -it -v $PWD:/source -w /source amberframework/amber:0.35.0 bash
產生出來的程式裡面就有 Dockerfile 了,真的很貼心:
FROM amberframework/amber:0.35.0
WORKDIR /app
# shards 是 Crystal 的套件管理工具
COPY shard.* /app/
RUN shards install
COPY . /app
RUN rm -rf /app/node_modules
# Amber 起本機測試服務,並開啟動態編譯功能
CMD amber watch
Amber 因為有提供 Dockerfile,所以就相較單純了點,只有特別去了解 shards
指令是套件管理工具(之前的版本是 crystal deps
),以及了解 amber
指令有什麼功能。
Rocket 使用 Rust 撰寫。Phoenix、Amber 與 Lapis 都有提供框架專屬的整合指令,但 Rocket 沒有,但 Rust 內建的套件管理工具 Cargo 有基本專案初始化指令,可以從這裡開始建立專案:
# 使用 cargo 建立專案
cargo new rocket --bin
再來 Cargo.toml
與 main.rs
檔,照著官方教學輸入內容,最後執行即可啟動開發用的 server:
cargo run
主要都是用 Cargo,所以可以直接拿 Rust image 來執行指令:
docker run --rm -it -v $PWD:/source -w /source rust:1.46 bash
FROM rust:1.46
WORKDIR /usr/src/app
ENV USER=dummy
# Rocket 官方教學有提到要開 nightly
# 會在這裡執行 cargo init,這是 cargo build 一個 issue
RUN rustup default nightly && cargo init --bin --name dummy .
COPY Cargo.* ./
# 下載並編譯依賴
RUN cargo build
COPY . .
# 編譯主程式
RUN cargo build
CMD ["cargo", "run"]
這個框架遇到了兩個麻煩事,第一個是昨天提的 Alpine 問題,筆者總算遇到了,在 cargo build
編譯的時候出錯,改成 Debian 就正常了。
另一個則是,原本想跟其他框架一樣,把依賴 COPY Cargo.* ./
跟複製檔案 COPY . .
切成兩個階段,但 cargo build
會需要存在一個 src/main.rs
檔,才能正常執行。一個很笨,但很有效的方法:cargo init
一個 dummy 專案即可。
Lapis 使用 Lua 撰寫。Docker 官方並沒有出 Lua image,因此筆者有手癢寫了 Lua image。
Lapis 參考官方文件,建置與執行的指令如下:
lapis new
# 使用 MoonScript 需要編譯
moonc *.moon
lapis server
Lapis 官方也沒出 image,筆者自己有寫了一個 Lapis image,可以直接拿來用:
Lapis 的 Dockerfile 比較複雜,有興趣可以看 Dockerfile。
docker run --rm -it -v $PWD:/source -w /source mileschou/lapis:alpine sh
FROM mileschou/lapis:alpine
WORKDIR /usr/src/app
COPY . .
RUN moonc *.moon
CMD ["lapis", "server"]
Lapis 因為最麻煩的環境,筆者以前已經搞定了,所以這邊的 Dockerfile 就能寫得很簡單。
今天的程式碼有放上 GitHub,有興趣可以去下載回來 build 看看。
雖然開頭有提到,今天要示範 Docker 的 IaC 特性,但筆者額外想提的觀念是:身為開發人員,想要使用既有的 Docker image 或是撰寫 Dockerfile,不只是要學好 Docker 而已,對程式工具、執行流程以及相關錯誤訊息都要有清楚的認知,才能真正有效發揮出 Docker 的優點。