以 Laravel 為例,來 build image 吧!

Laravel 是目前 PHP 很流行的框架,今天以看到 Laravel 的預設歡迎頁為目標,建置 Laravel image。

  1. 初始化 Laravel
  2. 事前準備
  3. Dockerfile 的第一手
  4. 設定路徑與原始碼
  5. 設定啟動 server 指令

初始化 Laravel

首先一開始,要先把 Laravel 主程式先準備好。參考 Installing Laravel 文件,安裝 PHP 7.3 與 Composer,然後執行下面指令即可把 Laravel 程式安裝至 blog 目錄。

composer create-project --prefer-dist laravel/laravel blog

接著進 blog 目錄啟動 server:

cd blog

php artisan serve

完成後,看到 Laravel 的預設歡迎頁,程式碼就準備完成了。

事前準備

先定義 Docker 要下什麼指令,會達到跟官方執行 php artisan serve 一樣的結果:

docker run --rm -it -p 8000:8000 laravel

轉換成 docker-compose.yml 如下:

version: "3.8"

services:
laravel:
image: laravel
stdin_open: true
tty: true

其中 -p 8000:8000 是配合預設開 8000 port;image 取名為 laravel

撰寫 Dockerfile 的過程中,會不斷重複啟動與移除 container 測試,可以用下面 Makefile 來簡化指令,示範和說明也比較清楚一點:

#!/usr/bin/make -f
IMAGE := laravel
VERSION := latest

.PHONY: all build rebuild shell run

# ------------------------------------------------------------------------------

all: build

# 建置 day27
build:
docker build -t=$(IMAGE):$(VERSION) .

# 不使用 cache 建置 day27
rebuild:
docker build -t=$(IMAGE):$(VERSION) --no-cache .

# 執行並使用 shell 進入 container
shell:
docker run --rm -it -p 8000:8000 $(IMAGE):$(VERSION) bash

# 執行 container
run:
docker run --rm -it -p 8000:8000 $(IMAGE):$(VERSION)

Dockerfile 的第一手

DockerfileTDD 一樣有三循環如下:

  1. 新增 Dockerfile 指令
  2. 執行 docker build 並驗證是否正確
  3. 最佳化 Dockerfile

撰寫 Dockerfile 的第一手,是先寫一個可以驗證成功的 Dockerfile,後續就可以走上面的三循環。

昨天範例有提到 FROM 也是一個步驟。只要 image 在 DockerHub 能下載得到,Dockerfile 就能 build 成功,我們可以寫一個只有 FROMDockerfile。Laravel 官網的 Server Requirements 要求 PHP >= 7.3,因此我們使用 php:7.3 image:

FROM php:7.3

接著執行 make buildmake shell 試看看:

make build
make shell

建置完成,並在 commit 上 tag laravel,同時進去 shell 確認 PHP 版本正確。

註:目前 laravel tag 與 php:7.3 tag 在同個 commit 上。

設定路徑與原始碼

預設的路徑是根目錄 /,是個一不小心就會刪錯檔案的位置,可以換到一個比較安全的目錄,比方說 /source

WORKDIR /source
  • WORKDIR 可以設定預設工作目錄。它同時是 docker build 過程與 docker run 的工作目錄,跟 -w 選項的意義相同。

接著把 Laravel 原始碼複製進 container 裡,這裡使用 COPY 指令:

COPY . .
  • COPY 是把本機的檔案複製到 container 裡,使用方法為 COPY [hostPath] [containerPath]

要注意這裡有個雷,單一檔案複製沒有問題,但目錄複製就得小心。它的行為跟 Linux 常見的 cp 不大一樣。

cp -r somedir /some/path

cp 指令來說,上面指令執行完會多一個目錄 /some/path/somedir

COPY somedir /container/path
COPY somedir/* /container/path

COPY 指令來說,上面兩個指令是等價的。原本預期會多一個 /container/path/somedir 目錄,實際上是 somedir 目錄裡所有東西全複製到 /container/path 下。

解決方法是改成下面這個指令:

COPY somedir /container/path/somedir

設定啟動 server 指令

Docker Compose 裡有提到一個設定是 command,它定義了 container 啟動預設會執行的指令。Dockerfile 也有一樣用法的指令--CMD。而啟動 server 指令一開始 Laravel 建好的時候已經知道了,php artisan serve

套用在 CMD 指令上的用法會有兩種如下:

# exec 模式,官方推薦
CMD ["php", "artisan", "serve"]
# shell 模式
CMD php artisan serve

未來會再解釋這兩個模式的差異,先用官方推薦來試試。

實際執行的時候會發現不能正常連到 server?原因很簡單,在說明 Port forwarding 的時候,曾用了下面這張圖:

當時提到每個 container 有屬於自己的 port,因為每個 container 都是獨立的個體,包括 host 也是一個獨立的個體。

再回頭看 Laravel 啟動 server 的資訊,它綁定了 127.0.0.1:8000 在 container 上。

Starting Laravel development server: http://127.0.0.1:8000

不能連線的原因其實非常單純,host 與 container 要視為兩台不一樣的機器,因為 container 僅綁定本機--也就是只有進 container 使用 curl http://127.0.0.1:8000 可以連線,而 host 連 container 會被視為外部連線,因此會連線失敗。

解決方法很單純,把綁定 IP 調整即可,下例是以 0.0.0.0 全部開放為例:

CMD ["php", "artisan", "serve", "--host", "0.0.0.0"]

建置服務成功!測試也完成了,恭喜大家成功用 Dockerfile 建置 Laravel image 成功!

今日自我回顧

今天 Dockerfile 最後長相如下:

FROM php:7.3

WORKDIR /source
COPY . .

CMD ["php", "artisan", "serve", "--host", "0.0.0.0"]

目前這個內容有很多缺陷,接下來會開始做 Dockerfile 最佳化,會一步步讓讀者知道更多 image 與 container 相關的技巧。

  • 了解 build image 的流程
  • 演練實際程式 build image