自由學習的風

幽夢影 張潮 少年讀書,如隙中窺月;中年讀書,如庭中望月;老年讀書,如臺上玩月。皆以閱歷之淺深,為所得之淺深耳。

Laravel 排程在 Docker 中執行

2026年7月28日 星期二

Laravel 專案有排程要執行,但是 php-fpm container 裡沒有 crontab 指令,這是很常見的情況。

這時候不要急著把 cron 裝進 php-fpm。php-fpm 本來是處理網站 request 的,排程可以用別的方式做。

需求:

要定時執行:

php artisan forum:sync

例如每天 4 點和 15 點各執行一次。

方法一:由 Linux 主機 crontab 執行

可以直接在 host 的 crontab 裡呼叫 container 裡的 artisan:

docker exec -w /var/www/default php-fpm php artisan forum:sync

先手動測一次:

docker exec -w /var/www/default php-fpm php artisan forum:sync

如果可以正常執行,再加入 crontab:

crontab -e

每天 04:00 和 15:00 執行:

0 4,15 * * * /usr/bin/flock -n /tmp/forum-sync.lock /usr/bin/docker exec -w /var/www/default php-fpm php artisan forum:sync >> /var/log/forum-sync.log 2>&1

指令說明:

  1. 0 4,15 * * *

每天 4 點和 15 點執行。

  1. /usr/bin/flock -n /tmp/forum-sync.lock

避免上一輪還沒跑完,下一輪又啟動。

  1. /usr/bin/docker exec

從 host 執行 container 裡的指令。

  1. -w /var/www/default

指定 Laravel 專案所在目錄。

  1. >> /var/log/forum-sync.log 2>&1

把執行結果和錯誤訊息寫入 log。

方法二:建立 scheduler container

如果想比較 Docker 化,也可以另外建立一個 scheduler service:

laravel-scheduler:
  image: "${PHP_VERSION}"
  container_name: laravel-scheduler
  working_dir: /var/www/default
  volumes:
    - ./wwwroot:/var/www
    - ./session:/var/lib/php/session
  environment:
    TZ: ${TZ}
  command: sh -c "while true; do php artisan schedule:run --verbose --no-interaction; sleep 60; done"
  restart: unless-stopped
  depends_on:
    - mysql

啟動:

docker compose up -d laravel-scheduler

這種方式適合有使用 Laravel scheduler,也就是在 app/Console/Kernel.php 裡集中管理排程的專案。

常見錯誤:

如果出現:

Could not open input file: artisan

代表 -w /var/www/default 不是 Laravel 專案所在目錄,要改成實際路徑。

如果實際使用 PHP 8.3 container,就要把 container 名稱改成:

php83-fpm

結論:

php-fpm 裡沒有 crontab 不算問題。

簡單需求可以用 host crontab 呼叫:

docker exec ...

比較完整的 Laravel 排程,則可以用獨立 scheduler container。

不要把所有功能都塞進 php-fpm,之後比較好維護。

PHP-FPM Docker image 選擇與重建


最近整理 PHP-FPM 的 Dockerfile,順便把 PHP 8.4、PHP 8.2、PHP 8.3、PHP 7.4 的 image 選擇記錄一下。

PHP 8.4 要選哪一個?

如果是一般 Laravel、Symfony、WordPress 或自己寫的 PHP 網站,建議先用:

FROM php:8.4-fpm

如果想更明確,可以寫:

FROM php:8.4-fpm-trixie

Debian-based image 比較適合一般網站環境,extension 和系統套件比較不容易踩到奇怪的相容性問題。

Alpine base 呢?

Alpine image 比較小,例如:

FROM php:8.4-fpm-alpine

但 Alpine 使用 musl libc,有時 PHP extension、Composer package 或外部 binary 會比較麻煩。

除非真的很在意 image 大小,不然一般網站我會先選 Debian 版。

不建議用 buster

buster 是比較舊的 Debian 系列,不建議再拿來當新的 PHP 8.4 image 基礎。

如果要固定 Debian 版本,可以考慮:

FROM php:8.4-fpm-trixie

或依需求使用 bookworm 系列。

官方 image 更新後要重新 build

如果官方 php:8.4-fpm 有更新,本機自建 image 不會自己變新。

要重新 build,建議加上 --pull:

docker build --pull -f Dockerfile_php84-fpm -t tiebob/php:8.4-fpm .

如果用 Compose:

docker compose build --pull
docker compose up -d

如果想完全不用 cache:

docker compose build --pull --no-cache

Dockerfile 整理重點:

這次整理 Dockerfile 時,主要做了幾件事:

  1. 明確指定 base image
  2. apt-get update、apt-get install、清 cache 放在同一層
  3. 加上 --no-install-recommends
  4. 移除 production image 不必要的 vim
  5. Composer 改成從官方 image 複製

Composer 可以這樣裝:

COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer

這樣比在 Dockerfile 裡 curl installer 清楚一些。

PHP 7.4:

後來也建立了 PHP 7.4 的 Dockerfile:

FROM php:7.4-fpm-bullseye

不過 PHP 7.4 已經是 EOL,只建議給舊系統短期使用,不適合新的正式服務。

所以如果專案裡還有 PHP 7.4,最好在檔案裡加註解,提醒自己這是 legacy 用途。

結論:

我的選擇大概是:

一般網站:php:8.4-fpm 或 php:8.4-fpm-trixie
想省 image 大小:php:8.4-fpm-alpine
舊系統相容:php:7.4-fpm-bullseye,但要標示風險

Image 不一定要追最新,但版本和用途要寫清楚,之後維護才不會搞混。

Docker container 的 UID/GID 權限整理

示意圖:Docker UID/GID 權限關係


這次整理 Docker 多網站環境時,遇到一個很常見的問題:不同 container 裡的使用者 UID/GID 不一樣,結果 bind mount 到 host 的檔案權限就容易亂掉。

問題:

目前有這些服務:

  1. nginx
  2. php-fpm
  3. php82-fpm
  4. php83-fpm
  5. mysql

PHP-FPM 大多已經是:

uid=1000(www) gid=1000(www)

但是 Nginx worker 可能是:

uid=101(nginx) gid=101(nginx)

MySQL 則是自己的:

uid=27(mysql) gid=27(mysql)

這時候如果 PHP 建立檔案、Nginx 要讀檔,或者 host 要直接修改檔案,就可能遇到權限問題。

原則:

不要把所有 container 都硬改成同一個 UID。

應該分成兩類:

網站檔案:
nginx、php-fpm、php82-fpm、php83-fpm

資料庫資料:
mysql

也就是網站相關服務統一,MySQL 保持自己的使用者。

建議目標:

nginx worker -> 1000:1000
php-fpm      -> 1000:1000
php82-fpm    -> 1000:1000
php83-fpm    -> 1000:1000
mysql        -> 保持 mysql 預設 UID/GID

網站目錄可以整理成:

sudo chown -R 1000:1000 ./wwwroot
sudo chown -R 1000:1000 ./session
sudo chown -R 1000:1000 ./log/nginx

MySQL 資料目錄不要一起改。

如果 MySQL container 裡查到是 27:27:

sudo chown -R 27:27 ./data/mysql/data

Nginx 注意事項:

原本以為可以直接在 nginx.conf 寫:

user 1000 1000;

結果啟動時出現:

getpwnam("1000") failed

原因是 Nginx 的 user 指令會去找使用者名稱,不是單純用數字 UID。

比較好的方式是自建 Nginx image,在 image 裡建立一個 www 使用者,UID/GID 是 1000:1000,然後 nginx.conf 寫:

user www www;

驗證:

先讓 PHP 建立測試檔:

docker compose exec php-fpm sh -c 'echo ok > /var/www/permission-test.txt'

再讓 Nginx 讀取:

docker compose exec nginx sh -c 'cat /var/www/permission-test.txt'

回 host 看 owner:

ls -lan ./wwwroot/permission-test.txt

如果看到:

1000 1000

而且 Nginx 也能讀到 ok,表示權限大致整理好了。

結論:

遇到 Docker 權限問題,不要先用:

chmod -R 777

這只是把問題蓋掉。

比較好的做法是先弄清楚誰要讀、誰要寫,再把共同使用同一批檔案的 container 對齊 UID/GID。

Docker 多網站部署架構整理

示意圖:Docker 多網站部署架構








圖片檔 blog-drafts/images

目前在 Linux 主機上利用 Docker 跑多個網站,這次順手把目前的建置方式整理一下,避免之後要調整時又要重新看一次。

環境:

  1. Linux 主機
  2. Docker / Docker Compose
  3. Nginx 或 Caddy 做 Web 入口
  4. 多個 PHP-FPM container
  5. MySQL container

目前架構:

大致上是這樣:

Browser
  ↓
Nginx / Caddy
  ↓
依不同網站轉給不同 PHP-FPM
  ↓
MySQL

這樣的做法有點像把傳統的 LEMP 主機,改成用 Docker 來管理。

目的:

  1. 一台主機可以放多個 PHP 網站
  2. 不同網站可以使用不同 PHP 版本
  3. 網站目錄、log、資料庫資料都放在 host 上,方便備份
  4. Web server 統一處理 80、443、SSL、virtual host
  5. 需要瀏覽器自動化時,可以用 geckodriver container

目前比較像「共享主機 Docker 化」,不是每個網站都獨立一整組服務。

建議調整:

1. 不要使用 latest

例如:

NGINX_VERSION=nginx:latest

建議改成固定版本:

NGINX_VERSION=nginx:1.29.3-alpine

不然之後重新 build 或 pull 時,可能會拉到不同版本,問題會比較不好追。

2. PHP 7.4 要列為舊系統

如果還有網站需要 PHP 7.4,可以先保留,但要知道這已經是 legacy 環境。

建議做法:

  1. 先列出哪些網站還需要 PHP 7.4
  2. 確認不能升級的原因
  3. 有計畫地改到 PHP 8.2 / 8.3 / 8.4

3. MySQL 不一定要開給外部

如果只有 container 裡的網站會連 MySQL,通常不需要:

ports:
  - 3306:3306

比較保守可以改成:

ports:
  - 127.0.0.1:3306:3306

或者乾脆不要 publish port,只讓 Docker network 內部連線。

4. volumes 和 bind mount 要整理

目前像這些目錄都會影響備份:

wwwroot/
data/mysql/data/
etc/nginx/
etc/caddy/
log/
session/

建議之後整理一份備份清單,才不會只備份 image,卻漏掉真正重要的網站資料。

結論:

這個架構是可行的,而且蠻適合維護多個 PHP 網站。

接下來優先處理的順序,我覺得可以是:

  1. 固定 image 版本
  2. 整理 Nginx / PHP-FPM 權限
  3. 收斂 MySQL 對外連線
  4. 補上 healthcheck
  5. 將 PHP 7.4 網站逐步升級
  6. 評估改用 Caddy 簡化 HTTPS

先把基礎整理好,之後要搬主機、重建服務或排除錯誤都會輕鬆很多。

VS Code 直接透過 sftp 同步到遠端伺服器

2024年9月29日 星期日

環境介紹:

  • 正式機:10.x.y.aaa,  Linux Ubuntu + Docker(Nginx + PHP + MySQL)
  • 開發機:10.x.y.bbb, Windows 11 + Laragon + VS Code

之前利用 VS Code 開發之前,總是利用 SSHFS-Win 掛載至本機後,再用比對軟體檢查哪些檔案有修改,是否需要上傳(註:freecommander 很好用)。

不過,有時只是一、二支程式需要上傳,總覺得這樣做的話有點繁瑣,最近發現有支 vscode 的 extension(擴充套件):sftp

可以 ssh 連到遠端伺服器,直接上傳,也支援多個網站,所以可以先上傳到測試伺服器,沒問題後再上傳到正式機。

安裝後要先設定遠端伺器資訊,按【F1】,選擇「SFTP: Config」,會在 .vscode 目錄中產生 sftp.json 設定範本檔,直接依個人需求填入設定值即可。


預設是只會有一組伺服器設定,若有多組伺服器的話,例如有測試機、正式機…等,那就要設定 profiles,把設定檔改成下列即可,連線時,按按【F1】,選擇「SFTP: Set Profile」,選擇要連線的伺服器即可。


這支擴充套件還提供其它蠻方便的功能,也可以去發掘喔!

利用 Docker 快速建立 Sql Server 環境

2023年2月16日 星期四

環境:

  1. Ubuntu Linux 2022
  2. Docker CE 23.0.1

Docker 映像檔:

  • mcr.microsoft.com/mssql/server:2022-latest

操作:

  1. 下載 SQL Server 2022 Image
    $ docker pull mcr.microsoft.com/mssql/server:2022-latest
  2. 執行容器(指定參數)
    $ docker run \
    -e "ACCEPT_EULA=Y" \
    -e "MSSQL_SA_PASSWORD=<YourStrong@Passw0rd>" \
    -p 1433:1433 --name sql1 --hostname sql1 \
    -d \
    mcr.microsoft.com/mssql/server:2022-latest

  3. 檢查容器執行狀態
    $ docker ps  
  4. 安裝成功後,可下載或利用 chocolatey 來安裝 Sql Studio Management Studio
    c:\> choco install sql-server-management-studio
  5. 連線至 SQL Server 容器

容器執行參數說明:

  1. -e ACCEPT_EULA= 同意使用者授權合約
  2. -e MSSQL_SA_PASSWORD=<YourStrong@Passw0rd> 設定 SQL Server 最高管理員 sa 的密碼
  3. --name sql1 設定容器名稱
  4. --hostname sql1 設定容器主機名稱
  5. -p 1433: 1433 設定 port 的對應
  6. -d 設定容器以背景模式執行
  7. mcr.microsoft.com/mssql/server:2022-latest  SQL Server 映像檔來源與名稱

進階:

1. 掛載 volume 保存資料庫內容

  1. host 建立掛載的目錄
    $ mkdir data_mssql
  2. 【必須】設定該目錄擁有者, 預設owner id 為 10001
    $ sudo chown -R 10001 data_mssql
  3. 停止、刪除容器後,加上掛載 volume 參數後重新啟動容器
    $ docker run \
    -e "ACCEPT_EULA=Y" \
    -e "MSSQL_SA_PASSWORD=<YourStrong@Passw0rd>" \
    -p 1433:1433 --name sql1 --hostname sql1 \
    -v ./data_mssql:/var/opt/mssql
    -d \
    mcr.microsoft.com/mssql/server:2022-latest

2. 利用 docker-compose 執行容器

1. 建立 docker-compose.yml 檔案,內容如下:
version: '3.5'

services:
  mssql:
    image: mcr.microsoft.com/mssql/server:2022-latest
    restart: always
    ports:
      - 1433:1433
    volumes:
      - ./data_mssql:/var/opt/mssql
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=PutYourPassword
      - MSSQL_PID=Express
2. 執行

$ docker-compose up -d 


VS Code integreted terminial 的環境變數沒有更新

2023年1月15日 星期日

執行環境:

  • Windows 11 21H2(22000.1042)
  • Laragon 5.0(6.0)
  • VSCode
  • CodeIgniter 4.1.9

在Windows環境上利用 Laragon 來建立 PHP 開發環境十分方便,可以快整切換 PHP 版本,程式撰寫則用  VSCode,本來都沒啥問題,不過,這兩天測試之前建立的 CodeIgniter 4,它的版本是  4.1.9,看官網已經出到  4.3.0,不過需要 PHP 7.4 以上,但是之前都在 PHP 7.3 開發,結果在 Laragon 切換成 PHP 7.4 後,VSCode integreted terminial 顯示的還是  PHP 7.3。

試了下列動作:

  1. 把 VSCode 全部關掉,重新再執行 => 無效
  2. 檢查電腦環境變數,PATH 沒有設定 PHP 路徑
  3. 將 PHP 7.4 路徑加入 PATH 環境變數 => 無效

本來以為是電腦出問題了,後來看到一份資料,需要從終端機直接執行 VSCode,讚!有效,解決了,平時我都是按 Win Key,再直接執行,沒想到執行方式不一樣會造成這種影響。