---
# System prepended metadata

title: '使用 Docker 部署專案的學習筆記03：<br>使用 Docker Compose 建立環境（上） (MySQL-MariaDB, PHP-Apache, Apache, phpMyAdmin)'
tags: [ubuntu, phpmyadmin, apache, 後端, 佈署, vue, MariaDB, 筆記, docker-compose, laravel, Docker, 前端, php]

---

# 使用 Docker 部署專案的學習筆記03：<br>使用 Docker Compose 建立環境（上） (MySQL-MariaDB, PHP-Apache, Apache, phpMyAdmin)


## 準備架設的服務

:point_down: **點開看內容介紹** :point_down: 

:::spoiler  **1. MySQL (MariaDB)**

- **服務內容**：
  - MySQL 是一個開源的關聯式資料庫管理系統，主要用於儲存和管理應用程式的資料。MariaDB 是 MySQL 的一個分支，具有相容性和額外的功能。
  - 在這個部署環境中，MySQL 提供了後端數據儲存，讓 Laravel 應用能夠有效地管理用戶資料、交易記錄等各種數據。

- **使用場景**：
  - 適合用於需要持久化資料的應用程式，如電子商務網站、內容管理系統 (CMS) 等。
:::

:::spoiler **2. phpMyAdmin**
- **服務內容**：
  - phpMyAdmin 是一個用於管理 MySQL 資料庫的 PHP 應用程式，提供了直觀的網頁介面，方便使用者進行資料庫的操作。
  - 它允許用戶執行 SQL 查詢、管理資料表、匯入或匯出數據等功能。

- **使用場景**：
  - 特別適合於開發和測試階段，能夠快速訪問和管理資料庫，對於需要頻繁調整資料庫結構或資料的開發者來說，非常便利。
:::

:::spoiler **3. PHP-Apache** (for Laravel)
- **服務內容**：
  - PHP-Apache 是一個整合了 PHP 的 Apache 伺服器，專為執行 PHP 應用程式而設。
  - 這個伺服器能夠處理 Laravel 框架的請求，並提供動態內容。

- **使用場景**：
  - 專門用於需要 PHP 支持的應用程式，例如 Laravel，讓應用能夠正確執行後端邏輯。
:::

:::spoiler **4. Apache** (for web)
- **服務內容**：
  - Apache 是一個開源的 HTTP 伺服器，負責處理前端網頁的請求。
  - 它能夠提供靜態和動態內容，並支持各種模組和擴展。

- **使用場景**：
  - 適合用於傳統的 HTML 網頁或使用 Vue 框架開發的單頁應用程式。
:::

<br>

>[!Tip] **提示**
前三個server主要是**後端**，第四個Apache serve是為了**前端**
如果專案架構不是前後端分離就不必裝第四項的Apache（PHP-Apache內含Apache）
主要是看有沒有**獨立的前端架構**

## 準備建立 Docker 環境

在開始之前，要先建立 Docker 環境的目錄結構。
我們先在/home 新增一個docker目錄，之後docker相關的控制就放在/home/docker

:::info
FTP的local_root也可以直接設置在這個目錄，**++權限記得755++**。
:::

### 目錄結構

預設目錄為 `/home/docker`，並在其下分別建立以下資料夾：

- **controller**: 用於存放後端專案，也就是Laravel專案。
- **web**: 用於存放前端專案，如果沒有獨立的前端專案可以不用建。

名字可以自己改，這邊先暫訂這樣。

### 目錄結構

```bash
/home/docker
├── controller     # 存放後端專案
└── web           # 存放前端專案
```

## 準備建置的檔案介紹

在本專案中，我們需要三個重要的檔案來建立 Docker 環境，分別是 `docker-compose.yml`、`Dockerfile` 和 `apache.config`。這些檔案的功能如下：

1. **docker-compose.yml**  
   此檔案用於定義和管理多個 Docker 服務，設定各個服務的環境變數、port映射和依賴關係。  
   **位置**：放置於 `/home/docker` 目錄下。

2. **Dockerfile**  
   此檔案用於定義如何建構 PHP 環境的映像檔，包括安裝所需的依賴項及配置。  
   **位置**：放置於 `/home/docker` 目錄下。

3. **apache.config**  
   此檔案用於配置 Apache 伺服器的設置，以便正確處理 PHP 檔案和前端請求。  
   **位置**：放置於 `/home/docker` 目錄下。
   
### 完整的目錄結構

```bash
/home/docker
├── controller
│   └── (其他後端專案檔案)
├── web
│   └── (前端專案檔案)
├── docker-compose.yml      # 定義和管理多個 Docker 服務
├── Dockerfile              # 用於定義如何建構 PHP 環境的映像檔
└── apache.config           # 用於配置 Apache 伺服器的設置, 為php-apache的設定檔
```

### 各個檔案內容

:::spoiler **3.1 docker-compose.yml**

```yaml=
version: '3.8'

services:
  db:
    image: mariadb
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: 預設root的密碼
      MYSQL_DATABASE: 建立的資料庫
      MYSQL_USER: 預設建立的帳號
      MYSQL_PASSWORD: 預設建立帳號的密碼
    ports:
      - "3307:3306"
    volumes:
      - mysql_data:/var/lib/mysql

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    restart: always
    ports:
      - "8082:80"
    environment:
      PMA_HOST: db
      PMA_AUTH_TYPE: cookie

  php:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./controller:/var/www/html
    ports:
      - "8081:80"
    depends_on:
      - db
    command: >
      sh -c "composer install && apache2-foreground"
    environment:
      APACHE_VHOST: /etc/apache2/sites-enabled/000-default.conf

  vue:
    image: httpd:2.4
    volumes:
      - ./web/dist:/usr/local/apache2/htdocs
    ports:
      - "8083:80"
    depends_on:
      - php

volumes:
  mysql_data:
```
:::

:::spoiler **3.2 Dockerfile**

```dockerfile=
FROM php:8.2-apache

# 安裝必要的包和 PHP 擴展
RUN apt-get update && apt-get install -y \
    zlib1g-dev \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    libxml2-dev \
    libzip-dev \
    libonig-dev \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install \
    gd \
    mysqli \
    pdo \
    pdo_mysql \
    zip \
    soap \
    bcmath \
    opcache \
    intl \
    mbstring \
    exif \
    calendar \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# 啟用 Apache mod_rewrite 模組
RUN a2enmod rewrite
RUN a2enmod headers

# 設定虛擬主機
COPY apache-vhost.conf /etc/apache2/sites-enabled/000-default.conf

# 設置工作目錄
WORKDIR /var/www/html

# 安裝 Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
```

:::


:::spoiler **3.3 apache.config**
```bash=
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html/public

    <Directory /var/www/html/public>
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
        
        # CORS 設定
        Header set Access-Control-Allow-Origin "http://192.168.1.123:8083"
        Header set Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, PATCH, DELETE"
        Header set Access-Control-Allow-Headers "Content-Type, Authorization"
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
```
:::

## 準備完成

把所有檔案建立好放在該放的地方
然後就可以進入下一步囉！

~~Docker都還沒開始建呢！~~

<br>

## 同場加映：一些踩到的坑

:::spoiler **從設定開始踩了不少的坑，從這邊開始條列式紀錄**

### 關於docker-compose.yml

>[!Note]
>yml是一種排版要求嚴格的格式，如果有發生什麼奇怪的無法執行的問題卻一直找不出來設定哪裡有錯，可能要檢查是不是縮排有跑掉或者哪裡沒空格好。

1. 為了使用交易功能把MySQL換成MariaDB，自作聰明的把
`volumes: - mysql_data:/var/lib/mysql` 改掉後docker一直報錯，
爬文才發現這兩個資料庫相容度高到可以什麼都不用改，改映像檔就好。

2. 承上，原本怕MariaDB和php會有相容性問題，所以去查MySQL5.7對應MariaDB是哪個版本。結果限定版本後在資料表匯入的時候反而發生相容性問題（...）
一怒之下不限定版本直接更新到最新版，就什麼問題也沒有了。 

3. 原本一直覺得已經有php-apache了我為什麼還要再掛一個apache，
後來發現如果直接掛在下面會需要多一層資料夾（/controller/vue），
或者要在前後端的專案裡各設置一個.htaccess 檔
覺得有點麻煩而且我想盡量把設定檔放在最外層，加上這樣會讓結構變得有點複雜
最後就乾脆多架一個apache for 前端，讓專案結構保持清晰、前後端盡量分離的狀態

### 關於Dockerfile

4. Dockerfile裡的那一串php擴展，是因為我的captcha圖片跑不出來去查才發現的
原本只是缺GD套件，裝了之後說需要其他相依套件；於是就乾脆讓GPT列出所有常用套件直接裝上去就什麼問題也沒了
覺得裝太多的可以查一下每個套件各自的作用來決定增減

5. `RUN a2enmod headers` 這項是為了CORS設定要啟動的模組，如果沒有前後端分離可以不用

6. Dockerfile裡安裝composer那行非常重要，關係到Laravel是否能執行

### 關於apache.config

7. apache.config這個檔案如果沒有前後端分離不用也沒關係，主要是用來處理CORS的問題
當然Laravel遇到一些伺服器問題需要設定apache的話也要開這個檔案

8. `AllowOverride None`這項預設是`All`的樣子，這是要不要讓apache去抓.htaccess的設定
如果沒有.htaccess就要關掉，不然會噴紅字無法啟動

    >[!Caution]241017修改
    >本來到處爬文內容是這樣沒錯，但是後來上到GCP發生一系列API無法使用的問題
    >查到最後發現`AllowOverride`的設定必須要設定成`All`
    >才不會被擋跨域。
    >神奇的是在Local端測試是可以使用的，但後續到GCP的時候卻全部被擋掉了。


9. apache.config裡CORS設定那三行就是血與淚的設定了，總之沒有前後端分離需要跨域請求API的話不用這三行也沒關係，**不過！**
    :::danger
    **如果打算要把老師上課做的SPA放到/web用ajax呼叫後端api，PHP寫的api放到/controller讓前端呼叫**，這樣就算跨域請求了。
    
    **解析：**
    /web給container vue使用，用的是apache伺服器，設定是8083 port；
    /controller設定是給container php使用，用的是php-apache伺服器，設定是8081port。
      
    　　**++相同IP不同port即視為跨域請求++！**
    :::
    之後就是一系列超麻煩的CORS Block之旅.....~~不過我都踩完了照著設定應該不會噴紅字...吧~~

10. 關於CORS的三行
    10-1. `Access-Control-Allow-Origin` : 設定要CORS放行的網域，在這裡就是前端網域
    10-2. `Access-Control-Allow-Methods` : 這邊沒設定有噴紅過，但是這邊如果設定了Origin就不能是'\*\'全域，必須限定網域（沒記錯的話是這樣）
    10-3. `Access-Control-Allow-Headers` : 設定CORS放行的自訂header，我有設定Authorization驗證，不放進去也會噴紅字……

    BTW，這三行也是Dockerfile要加入`RUN a2enmod headers`這個模組的原因。
:::


