「Container 的技術有利於合作。」,ㄏㄏ。

前言

系列文

文章適用對象

  • 使用 Laravel 開發 API 的後端工程師
    • 前端工程師僅僅「使用」這個環境,且前端與後端分屬不同的 Repo 或 Branch

實作

需求

  • Docker
    • 17.05 以上版本:因為使用 Multi-Stage Builds
  • Docker Compose

步驟

統一將環境放在 .docker/development 資料夾下(如果沒有這個資料夾請自行建立)

Step 1. 建立專案的 Dockerfile

在專案的根目錄下建立一個 Dockerfile,內容如下

1
2
3
4
5
FROM alpine

WORKDIR /code

COPY . .

這個用意主要是希望把專案的程式碼通通打包成一個 Image

為了避免把 vendor/, .git/ 的內容複製進去,可以在根目錄建立一個 .dockerignore

1
2
vendor/
.git/

使用 docker build -t {project_name} . 建構 Docker Image,然後確定該 Image 裡沒有包含任何的 vendor/.git 資料夾

※ 其實像 .idea/ 之類的也能加入,不過這個就看個人或專案性質

Step 2. 建立 Development 的 Dockerfile

建立 .docker/development/Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
FROM {project_name} AS code

FROM composer AS builder

WORKDIR /build

COPY --from=code /code /build

RUN composer install

FROM php:alpine

ENV APP_ENV=development \
    APP_DEBUG=true \
    APP_KEY="Input Your Application Key, Generated By Laravel"

WORKDIR /www

COPY --from builder /build /www

RUN docker-php-ext-install bcmath pdo_mysql && \
    php artisan config:cache && php artisan route:cache && php artisan view:cache

EXPOSE 8000

CMD ["php", "artisan", "serve"]

階段概述

  1. FROM {project_name} AS code 主要是將Step 1. 建立專案的 Dockerfile 所建立的 Docker Image 拿來使用
  2. FROM composer AS builder 主要是執行 Composer Install
  3. FROM php:alpine 主要是建立執行環境並且啟動 Built-in Server

建立 Dockerfile 之後,使用 docker build -t {project_name}/development . 建立 Development Docker Image。

Step 3. 設定 Development 的 docker-compose

建立 .docker/development/docker-compose.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
version: '3'

services:
    app:
        image: {project_name}/development
        ports:
            - 8000:8000
        depends_on:
            - database
        link:
            - database
        command: ["php", "artisan", "serve", "--host=0.0.0.0"]
    
    database:
        image: mysql
        volumes:
            - ./database/my.cnf:/etc/mysql/conf.d/custom.cnf
        environment:
            - MYSQL_ROOT_PASSWORD=root
            - MYSQL_DATABASE=homestead
    
    redis:
        image: redis

※ 因為 MYSQL_USERNAME 及 MYSQL_PASSWORD 所建立出來的使用者只能允許來自 127.0.0.1 的連線,所以這邊不使用 ※ 記得建立 .docker/development/database/my.cnf 其格式與上一篇文章相同

Step 4. 加入資料庫及 Redis 的環境設定

修改 .docker/development/Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
FROM {project_name} AS code

FROM composer AS builder

WORKDIR /build

COPY --from=code /code /build

RUN composer install

FROM php:alpine

ENV APP_ENV=development \
    APP_DEBUG=true \
    APP_KEY="Input Your Application Key, Generated By Laravel" \
    DB_HOST=database \
    DB_DATABASE=laravel \
    DB_USERNAME=root \
    DB_PASSWORD=root \
    REDIS_HOST=redis

WORKDIR /www

COPY --from builder /build /www

RUN docker-php-ext-install bcmath pdo_mysql && \
    php artisan config:cache && php artisan route:cache && php artisan view:cache

EXPOSE 8000

CMD ["php", "artisan", "serve"]

因為 Docker Compose 會將 service name 解析為 domain,所以在 DB_HOSTREDIS_HOST 的地方只需要填寫 docker-compose.yml 的 service name 即可。

Step 5. 執行時自動執行 Database Migrate

利用 Dockerfile 中的 ENTRYPOINT 功能,可以讓我們在 container 啟動後執行某些指令。

建立 .docker/development/entrypoint.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/sh

PHP_BINARY=/usr/local/bin/php
ARTISAN_FILE=/www/artisan

sleep 20

${PHP_BINARY} ${ARTISAN_FILE} migrate

exec "$@"

然後將此檔案設為可執行,chmod +x .docker/development/entrypoint.sh

修改 .docker/development/Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
FROM {project_name} AS code

FROM composer AS builder

WORKDIR /build

COPY --from=code /code /build

RUN composer install

FROM php:alpine

ENV APP_ENV=development \
    APP_DEBUG=true \
    APP_KEY="Input Your Application Key, Generated By Laravel"

WORKDIR /www

COPY --from builder /build /www
COPY entrypoint.sh /entrypoint.sh

RUN docker-php-ext-install bcmath pdo_mysql && \
    php artisan config:cache && php artisan route:cache && php artisan view:cache

EXPOSE 8000

ENTRYPOINT ["/entrypoint.sh"]

CMD ["php", "artisan", "serve"]

※ 在 entrypoint.sh 中,用了 sleep 20 是因為 MySQL 啟動需要一點時間,必須等待它啟動之後才能執行 migrate ※ entrypoint.sh 中還可以放入其它指令,例如執行 Database Seeding

Step 6. 啟動服務

現在,可以利用以下指令來啟動相應服務

1
docker-compose --file .docker/development/docker-compose.yml up

成功啟動之後,記得用 docker logs {project_app_container} 確定 migration 是否有執行成功。

如果沒有成功的話,可以重新啟動 app 那個 container(database 就不需要重啟)

現在,可以將前端的 API Endpoint 指向 localhost:8000 就可以正常運作

整合

GitLab CI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
stages:
  - build-code
  - build

build-main-image:
  image: docker

  services:
    - docker:dind

  stage: build-code

  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

  script:
    - docker build -t registry.gitlab.com/group_name/repo_name .
    - docker push registry.gitlab.com/group_name/repo_name

build-development-image:
  image: docker

  services:
    - docker:dind

  stage: build

  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

  script:
    - docker build -t registry.gitlab.com/group_name/repo_name/development .docker/development
    - docker push registry.gitlab.com/group_name/repo_name/development