一隻箱子裡的貓,看著電腦螢幕

Schrödinger's Programmer

奔跑吧工程師,趁年輕跑得越遠越好

用 Docker 建立 Laravel 開發環境

介紹如何使用 Docker 建置基礎的 Laravel 的開發環境

Ray

docker and laravel logo

過去在本機開發 Laravel 專案,多數是直接在本機安裝所需要服務
而後來改以虛擬機為主,曾經官方文件推薦開發環境的 Homestead 是很多人第一次使用的環境

而後出現的 Laradock 就是一個容器化的 PHP 開發環境
裡面已經預先設定好了常用的容器,只需要修改一些設定就可以直接使用
可以說是 Laradock 開啟了 Laravel 的大容器時代,在目前最新版本的 Laravel 8 中直接內建了容器環境

Laravel Sail 是一個輕量級的 cli
為使用 PHP、MySQL 和 Redis 建置的 Laravel 專案提供了一個可以直接使用的容器,甚至不需要有 Docker 經驗

那為什麼還需要自己學 Docker 自己建環境呢?因為當初還沒有 Sail 可以用
在本機開發可能不會遇到需要調整設定檔的情況
但是如果你想要的服務 Laravel Sail 沒有提供
又或是想要把線上環境也改為使用 Docker,那就必須要知道怎麼用 Docker 建置環境
並且知道該怎麼配置相關服務


廢話一大串,正文開始

Laravel 專案環境建置

使用 docker-compose 建構 Laravel 專案環境
一個 Laravel 專案需要包含以下服務

  • nginx
  • php
  • redis
  • postgreSQL

資料庫選用 postgreSQL,如果想換成 mysql 的也有在程式碼中附上設定方式
啟動的時候二選一就好!

1. docker-compose.yml

首先先建立一個專案資料夾,為了好分辨就叫 docker_env
在 docker_env 資料夾內建立檔案 docker-compose.yml,當作 docker-compose 服務的設定檔

完整程式碼

# docker-compose.yml
version: '3'

networks:
  server:
  data:

services:
  nginx:
    container_name: docker_env_nginx
    image: nginx:1.18 # stable version
    networks:
      - server
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d/:/etc/nginx/conf.d/
      - ./nginx/ssl/:/ssl/
      - <local project path>:/var/www/html/<project name>
    restart: always

  php:
    container_name: docker_env_php
    build: ./php/
    expose:
      - 9000
    networks:
      - server
      - data
    volumes:
      - /trp/web/home/:/var/trp/
    restart: always

  redis:
    container_name: docker_env_redis
    image: redis:6.0 # stable version
    ports:
      - "6379:6379"
    networks:
      - data
    restart: always

  postgre:
    container_name: docker_env_postgre
    ports:
      - "5432:5432"
    image: "postgres:12.6"
    volumes:
      - /trp/database/dev/database_data:/var/lib/postgresql/data # persist data even if container shuts down
    networks:
      - data
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    restart: always
    
  mysql:
    container_name: docker_env_mysql
    ports:
      - "3306:3306"
    image: mysql:5.7.23
    volumes:
      - /var/lib/mysql
    networks:
      - data
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}

分段說明

這邊會就 services 內容做說明,也會一併把需要的設定做完
如果只把上面複製貼上就啟動服務的話必定會失敗的

nginx

使用 nginx 作為我們的 Web 伺服器程式

  nginx:
    container_name: docker_env_nginx                      # 將容器命名為 docker_env_nginx 
    image: nginx:1.18                                     # stable version
    networks:
      - server
    ports:
      - "80:80"
      - "443:443"                                         # 容器外主機的 port:容器內的 port
    volumes:
      - ./nginx/conf.d/:/etc/nginx/conf.d/                # 載入的 nginx 設定檔
      - ./nginx/ssl/:/ssl/                                # 使用的 ssl 憑證
      - <local project path>:/var/www/html/<project name> # 將專案掛載進容器裡面
    restart: always                                       # 自動重啟

ports 設定了對容器內外的 port 對應,上面的設定檔中比較看不出來
我們改成 8080:80 來說明的話就是把主機上的 8080 port 映射給 nginx 容器內的 80 port

volumes 區塊則是說明需要掛載進 Docker 容器中的檔案,表現方式一樣是 本機位置:容器內位置
第三行中我們把指定的 Laravel 專案掛載進容器的指定目錄下,如果你沒有 Laravel 專案的話先去建一個新專案

第二行掛載了如果本機也想要用 https 的網址,你可以自己產一組證書放在 docker_env/nginx/ssl 目錄下
這麼做雖然會顯示不安全,但是至少是 https 的網址

第一行我們把同層中的 nginx 資料夾掛載進「/etc/nginx/conf.d/」這是 nginx 服務使用的設定檔位置
所以我們要在 docker_env/nginx 資料夾中建立 nginx config 檔案

# dev.project.com.conf
server {
    listen 80;
    listen [::]:80;

    # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    root /var/www/html/<laravel project name>/public;
    index index.php index.html index.htm;

    ssl_certificate /ssl/ssl.crt;
    ssl_certificate_key /ssl/ssl.key;

    server_name dev.project.com;
    
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        try_files $uri /index.php =404;
        fastcgi_pass docker_env_php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }
}

可以看到,監聽 80 port 的部分,會直接使用 301 跳轉到 443 port a.k.a https 使用的 port
root 則是指定專案資料夾的位置,可以看到他的指定位置和剛剛在 docker-compose.yml 中設定的掛載路徑一樣
至於 public 是 Laravel 專案的程式進入點
使用的 ssl 憑證也是對應到掛載時指定的容器內部路徑

fastcgi_pass 是指定到有安裝 php-fpm 的容器 container_name,以及剛剛提供容器內部存取的 port
其名稱和使用的 port 不是固定的!

php

  php:
    container_name: docker_env_php
    build: ./php/
    expose:
      - 9000
    networks:
      - server
      - data
    volumes:
      - <local project path>:/var/www/html/<project name> # 將專案掛載進容器裡面
    restart: always

因為 php 容器不需要對外 port,所以使用 expose 讓容器的 9000 port 可以在 docker network 裡面被存取
volumes 的掛載方式和 nginx 服務一樣

而要使用 Laravel,我們需要額外安裝一些 PHP 的擴充套件
安裝套件這件事情會使用 build 指令,重新打包我們自己的 docker image
會安裝一些 Laravel 所需要的 PHP 擴充套件,而資料庫的擴充套件就依照所使用的資料庫做選擇
底下主要以 postgreSQL 做範例

這邊也會一併示範安裝 composer,如果需要的話自行開啟,但是因為 PHP 不需要提前編譯的特性
我們可以透過直接在本機下指令改變 volumes 的檔案,進而讓容器內部程式產生變更
除非有非要在容器內下指令的原因,否則是不建議安裝

在 docker_env 資料夾下建立 php 資料夾,新增檔案 Dockerfile

# Dockerfile
FROM php:7.4-fpm
WORKDIR /var
RUN apt-get update && apt-get install -y libpq-dev libpng-dev libzip-dev zip
RUN docker-php-ext-install pgsql pdo_pgsql gd zip\
&& docker-php-ext-enable opcache

# To use mysql, install pdo pdo_mysql instead of pgsql pdo_pgsql

# Install composer Latest
# RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
# && php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
# && php composer-setup.php \
# && php -r "unlink('composer-setup.php');" \
# && mv composer.phar /usr/local/bin/composer

database

  postgre:
    container_name: docker_env_postgre
    ports:
      - "5432:5432"
    image: "postgres:12.6"
    volumes:
      - /trp/database/dev/database_data:/var/lib/postgresql/data # persist data even if container shuts down
    networks:
      - data
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    restart: always
    
  mysql:
    container_name: docker_env_mysql
    ports:
      - "3306:3306"
    image: mysql:5.7.23
    volumes:
      - /var/lib/mysql
    networks:
      - data
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}

而資料庫的設定中 volumes 主要是在做資料持久化使用,當容器重啟時會去讀取裡面的資料
資料庫的相關環境參數設定則是在 environment 區塊中,可以看到有很多敏感資料
所以我們可以藉由在 docker_env 資料夾中建立不加入版本控制的 .env 檔案

# .env
DB_USER=db_user
DB_PASSWORD=db_password
DB_NAME=my_database

等號左邊的鍵值可以在 docker-compose.yml 裡面透過 ${鍵值} 呼叫,進而取得左邊設定的值

啟動容器

經過上面的步驟,我們設定了 nginx php 和資料庫的服務
確定 Docker 服務本身有在運作後,進入 docker_env 資料夾下執行

docker-compose up -d

如果一切正常,容器會在背景起動
這樣就完成了「使用 Docker 建置 Laravel 環境」的目標!

要檢查容器啟動狀況可以使用指令,看是不是全部運行中

docker ps -a

不過如果是我自己在寫新的 docker-compose.yml 設定檔,我會先使用

docker-compose up

讓 Log 即時顯示在畫面上,這時候要除錯就方便很多了!
通常會遇到的問題是 volume 的資料夾不存在,例如 nginx config、ssl、Laravel 專案

Laravel .env 設定

在完成 Docker 環境的啟動之後,就要設定 Laravel 專案的環境設定檔
開啟根目錄下的.env,修改內容,這邊資料庫一樣使用 postgreSQL 做範例
設定上使用 docker-compose.yml 中 postgre 服務的內容

  • DB_HOST:設定檔中的容器名稱 container_name
  • DB_PORT:設定檔中的 ports
  • DB_DATABASE:設定檔中的 POSTGRES_DB
  • DB_USERNAME:設定檔中的 POSTGRES_USER
  • DB_PASSWORD:設定檔中的 POSTGRES_PASSWORD
# .env
DB_CONNECTION=pgsql
DB_HOST=docker_env_postgre
DB_PORT=5432
DB_DATABASE=my_database
DB_USERNAME=db_user
DB_PASSWORD=db_password

如以一來 Laravel 專案就可以透過容器內部網路存取到資料庫所在的容器

設定本機測試用網址

用 Docker 建立 Laravel 環境 - 完成 Laravel 連線到 Docker 的資料庫 - 完成

如此一來就大功… 欸不對!我還沒上車 我要怎麼看專案的畫面?

接下來要做的就是讓我們可以用瀏覽器開啟本機 Laravel 專案的畫面
首先使用終端機指令,不管是 bash 還是 zsh

sudo vim /etc/hosts 

/etc/hosts 檔案裡面加入

127.0.0.1       dev.project.com

意思是要把後面定義的網址名稱,指向前面的 IP 位址
所以當瀏覽器網址列輸入 dev.project.com 的時候會將請求發送到本機的 localhost

那為什麼這樣就可以看到剛剛設定的 Laravel 專案呢?
因為我們在前面的 nginx config 設定中,有設定了 server_name 內容

server_name dev.project.com;

所以當監聽本機 80 和 443 port 的 Docker nginx 服務發現有請求進來
而且符合 server_name 區塊設定的內容,就會進行處理

如果你有另一個 nginx config 在 listen port 號的後面加上 default_server 如下

listen 80 default_server;

那代表當其他 nginx config 規則都沒有抓取到的時候,nginx 會使用這個設定檔
若是全部 config 都沒有設定成 default_server,那麼會使用「檔名排序最前面」的設定檔

如此一來應該就可以瀏覽 Laravel 專案的畫面了

執行 Laravel 指令

如果想執行 Laravel 中跟資料庫有關的指令,例如

php artisan migrate

首先要先進入 php 容器內

docker exec -ti docker_env_php bash

在容器內的專案目錄下運行指令,就可以看到指令被正確執行
若是沒有連進容器,直接執行 migrate 指令,則會得到錯誤訊息

SQLSTATE[08006] [7] could not translate host name "docker_env_postgre" to address: nodename nor servname provided, or not known

因為程式不認得「docker_env_postgre」這個 host 的真實位址,所以無法執行
如果我們把 .env 中的 DB_HOST 換成真實位置,透過 docker inspect 指令取得
就可以執行,但是每次容器重啟就會被刷新。看來並沒有解決問題

如果真的很懶,覺得連進容器執行也很麻煩,就是要直接在本機資料夾執行該怎麼辦呢?
再一次修改 etc/hosts 檔案,加上

127.0.0.1     docker_env_postgre           # 資料庫使用的容器名稱

完工啦,這樣在本機專案資料夾下執行 migrate 就會被指向本機容器


以上就是這次「用 Docker 建立 Laravel 開發環境」的全部內容
好幾個小時終於打完這一篇,真的是心血結晶
牽涉到很多伺服器知識,如果不熟的人建議還是去了解一下再進行操作
礙於篇幅只能簡單說明怎麼設定,跳過很多設定的詳細說明和作法
還有很多部分沒提到,像是 docker network 的切分和使用

最新文章

Category

Tag