)
最近在macOS上折騰Docker目標(biāo)很明確把NginxPHP這套經(jīng)典組合放到容器里跑通。折騰完發(fā)現(xiàn)這類教程網(wǎng)上雖然不少但很多只給命令不講為什么遇到502和404容易一臉懵。這篇文章把從安裝Docker到最終訪問PHP頁面的全過程寫一遍把我踩過的坑、改過的配置、排查問題的順序都記錄下來適合剛接觸容器、想在本地快速跑起PHP環(huán)境的開發(fā)者參考。1. 安裝Docker先確認(rèn)芯片型號(hào)再動(dòng)手macOS上跑Docker和Linux上有本質(zhì)區(qū)別。Linux本身支持容器macOS內(nèi)核不支持Linux容器所以必須借助一層輕量虛擬機(jī)。Docker Desktop在macOS上就是做了這件事后臺(tái)跑一個(gè)Linux虛擬機(jī)所有容器都在這臺(tái)虛擬機(jī)里運(yùn)行。你可以把容器理解成標(biāo)準(zhǔn)化的貨柜而Linux內(nèi)核是碼頭裝卸設(shè)備macOS沒有這套設(shè)備只能先虛擬一個(gè)碼頭出來。這個(gè)底層機(jī)制搞清楚之后后面遇到文件掛載、資源占用這些問題就比較好理解了。安裝前第一件事是先確認(rèn)自己這臺(tái)Mac的芯片型號(hào)。命令行執(zhí)行uname -m輸出arm64表示Apple SiliconM系列芯片輸出x86_64表示Intel芯片。這個(gè)信息在后面拉鏡像、遇到平臺(tái)兼容問題時(shí)很關(guān)鍵。普通用戶直接裝Docker Desktop一般不需要手動(dòng)指定架構(gòu)Docker會(huì)自動(dòng)根據(jù)當(dāng)前架構(gòu)拉取對(duì)應(yīng)平臺(tái)的鏡像但偶爾會(huì)碰到某些老鏡像只有x86_64版本。Apple Silicon機(jī)器上可以加platform: linux/amd64強(qiáng)制跑代價(jià)是性能差一些本地調(diào)試勉強(qiáng)能用。提前知道這個(gè)知識(shí)點(diǎn)遇到報(bào)錯(cuò)時(shí)就不會(huì)一頭霧水。1.1 用Docker Desktop安裝還是用輕量工具新手我推薦直接用Docker Desktop。原因很直接安裝簡(jiǎn)單自帶圖形界面網(wǎng)絡(luò)、磁盤、資源占用都能可視化調(diào)整對(duì)剛接觸容器的人最友好。安裝包從官方頁面下載dmg雙擊拖進(jìn)Applications即可。第一次啟動(dòng)會(huì)提示允許一些擴(kuò)展權(quán)限需要輸入本機(jī)密碼這個(gè)不是異常直接確認(rèn)。如果不想用圖形界面或者覺得Docker Desktop太重也可以考慮Colima這類輕量方案。Colima在命令行里啟動(dòng)一個(gè)Linux虛擬機(jī)和Docker CLI配合使用。優(yōu)點(diǎn)是資源占用小、命令可控缺點(diǎn)是對(duì)不熟悉命令行的人不友好而且配置網(wǎng)絡(luò)端口時(shí)偶爾需要額外參數(shù)。我的建議是本地學(xué)習(xí)階段先用Docker Desktop跑通等理解了容器基本概念再考慮輕量方案沒必要一開始就給自己增加心智負(fù)擔(dān)。安裝完之后打開Docker Desktop等右下角圖標(biāo)變成穩(wěn)定狀態(tài)再在終端驗(yàn)證docker --version docker compose version如果兩個(gè)命令都能輸出版本號(hào)說明安裝成功。還可以跑一個(gè)最小鏡像確認(rèn)整個(gè)鏈路是通的docker run --rm hello-world這個(gè)鏡像體積很小輸出一段歡迎信息就退出了。這一步能同時(shí)驗(yàn)證客戶端、守護(hù)進(jìn)程、鏡像拉取這幾個(gè)核心環(huán)節(jié)是否正常。我第一次裝完跑這一步的時(shí)候卡了很久后來發(fā)現(xiàn)是虛擬機(jī)還沒完全啟動(dòng)多等幾秒再試就好了。1.2 鏡像版本選擇為什么推薦alpine項(xiàng)目里要用Nginx和PHP的官方鏡像具體標(biāo)簽有很多種。我推薦Nginx用nginx:stable-alpinePHP用php:8.2-fpm-alpine。alpine是種極簡(jiǎn)Linux發(fā)行版體積小鏡像通常只有幾十兆拉取快、占用磁盤少很適合本地調(diào)試。Debian系鏡像功能更全但體積動(dòng)輒幾百兆本地起個(gè)環(huán)境完全沒必要用那么重的東西。選php:8.2-fpm-alpine而不是php:8.2-apache也很講究。前者只帶PHP-FPM不包含Web服務(wù)器正好和Nginx搭配后者自帶Apache如果你不需要Apache就別用。項(xiàng)目里如果只是跑PHP腳本、不接數(shù)據(jù)庫直接拿官方鏡像就能用如果以后要裝PDO、mysqli這類擴(kuò)展就需要自己基于官方鏡像寫一個(gè)Dockerfile。這個(gè)后面的章節(jié)會(huì)詳細(xì)說。2. 先把項(xiàng)目目錄和配置規(guī)劃好很多新手學(xué)Docker時(shí)習(xí)慣在一堆命令里直接docker run拼參數(shù)容器一多就變成一坨看不懂的咒語。我經(jīng)歷過這些之后強(qiáng)烈建議用docker-compose來管理。它的核心價(jià)值是把多個(gè)容器的啟動(dòng)參數(shù)寫進(jìn)一個(gè)yaml文件一條命令啟動(dòng)、一條命令停止換臺(tái)機(jī)器也能復(fù)現(xiàn)同樣的環(huán)境。2.1 一個(gè)干凈到能直接抄作業(yè)的目錄結(jié)構(gòu)在動(dòng)手寫配置前先在本地建一個(gè)項(xiàng)目目錄。我的習(xí)慣是web/ ├── docker-compose.yml ├── conf/ │ └── nginx/ │ └── default.conf └── www/ └── index.phpconf放Nginx相關(guān)配置www放PHP代碼文件docker-compose.yml放容器編排配置。為什么刻意把配置和代碼分開因?yàn)楹竺鍺ginx容器需要把配置文件掛載進(jìn)去如果把配置放在代碼目錄里混著修改一個(gè)文件就會(huì)牽動(dòng)整個(gè)項(xiàng)目結(jié)構(gòu)后期維護(hù)很麻煩。分開之后代碼目錄干干凈凈配置變更也能單獨(dú)追蹤。代碼文件在www/index.php里隨便放一段測(cè)試內(nèi)容?php echo hello, nginx php in docker;先不放phpinfo()這種大頁面等下排查問題時(shí)會(huì)更清晰。等確認(rèn)能跑通了再換成phpinfo()驗(yàn)證擴(kuò)展和環(huán)境變量也不遲。2.2 鏡像選型的三個(gè)判斷標(biāo)準(zhǔn)鏡像怎么選我總結(jié)成三點(diǎn)。第一看基礎(chǔ)系統(tǒng)alpine精簡(jiǎn)、debian萬全本地優(yōu)先alpine。第二看進(jìn)程模型Nginx要選官方nginx鏡像PHP不能選php:apache那種自帶Web服務(wù)器的而選php:fpm或php:fpm-alpine。第三看版本要用自己熟悉的PHP版本比如項(xiàng)目依賴?yán)险Z法就選php:7.4或php:8.0沒有特殊要求就選8.2穩(wěn)定且社區(qū)資料多。這里還有個(gè)容易忽略的點(diǎn)不要一看到“官方”兩個(gè)字就以為鏡像里什么都有。PHP官方鏡像默認(rèn)只帶很基礎(chǔ)的擴(kuò)展數(shù)據(jù)庫驅(qū)動(dòng)、GD庫這些大部分都沒有。后面如果發(fā)現(xiàn)php -m里缺擴(kuò)展不要慌這是默認(rèn)狀態(tài)需要自己加。這部分我會(huì)在3.4小節(jié)給出一個(gè)可以直接用的Dockerfile。2.3 nginx為什么必須搭配php-fpmNginx本身不執(zhí)行PHP代碼它收到一個(gè).php請(qǐng)求時(shí)只會(huì)把它當(dāng)作一個(gè)普通文件要么返回內(nèi)容要么404。要讓Nginx把PHP請(qǐng)求交給真正的解析器就得靠FastCGI協(xié)議。PHP-FPM就是PHP的FastCGI進(jìn)程管理器它監(jiān)聽一個(gè)端口默認(rèn)9000等待Nginx把請(qǐng)求轉(zhuǎn)發(fā)過來然后解析PHP并把結(jié)果原路返回。用生活場(chǎng)景打比方Nginx是前廳接待負(fù)責(zé)收請(qǐng)求、處理靜態(tài)文件這些“迎客”的事PHP-FPM是后廚負(fù)責(zé)真正“做菜”也就是執(zhí)行PHP腳本。兩者之間通過一條固定的傳菜窗口通信這個(gè)窗口就是端口9000。在Docker編排里Nginx容器和PHP容器需要處于同一個(gè)容器網(wǎng)絡(luò)里Nginx才能訪問到php:9000這個(gè)地址。這就是為什么compose里要顯式聲明網(wǎng)絡(luò)而不是簡(jiǎn)單把兩個(gè)容器跑起來就完事。3. 用docker-compose把兩個(gè)容器編排起來3.1 docker-compose.yml逐段拆解直接在項(xiàng)目根目錄創(chuàng)建docker-compose.ymlservices: nginx: image: nginx:stable-alpine container_name: web-nginx ports: - 8080:80 volumes: - ./www:/var/www/html:ro - ./conf/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - php networks: - webnet php: image: php:8.2-fpm-alpine container_name: web-php volumes: - ./www:/var/www/html networks: - webnet networks: webnet: driver: bridge不用寫頂部version字段新版docker compose已經(jīng)忽略它了寫了反而容易誤導(dǎo)。services下面定義兩個(gè)服務(wù)nginx和php。nginx里image指定用哪個(gè)鏡像container_name給容器起名ports把宿主機(jī)8080端口映射到容器內(nèi)80端口。為什么宿主機(jī)不直接用80因?yàn)閙acOS上很多軟件會(huì)占用80端口或者權(quán)限限制導(dǎo)致綁定失敗。用8080這種高位端口能避開絕大多數(shù)沖突。volumes這段是精華。./www:/var/www/html:ro把宿主機(jī)當(dāng)前目錄下的www目錄掛載進(jìn)Nginx容器內(nèi)的/var/www/html只讀./conf/nginx/default.conf掛載為Nginx的default.conf只讀。PHP容器也把./www掛載進(jìn)/var/www/html但沒加:ro因?yàn)镻HP-FPM可能要寫文件。兩個(gè)容器共享同一個(gè)宿主機(jī)目錄Nginx才能把請(qǐng)求轉(zhuǎn)給PHP后正確找到對(duì)應(yīng)的腳本文件。3.2 先搞清楚depends_on的局限性depends_on: - php表示Nginx容器會(huì)在PHP容器啟動(dòng)之后才啟動(dòng)。但注意它只管啟動(dòng)順序不管服務(wù)是否已經(jīng)準(zhǔn)備好接收請(qǐng)求。PHP-FPM容器的9000端口可能要過幾秒才真正就緒這時(shí)候Nginx可能已經(jīng)啟動(dòng)、嘗試連接php:9000但失敗。表現(xiàn)在瀏覽器上通常是短暫的502過幾秒刷新就好了。如果希望避免這個(gè)“啟動(dòng)瞬間的502”可以給PHP容器加一個(gè)健康檢查讓Nginx等待它健康后再啟動(dòng)。compose標(biāo)準(zhǔn)里有healthcheck和condition: service_healthy的寫法但對(duì)于本地開發(fā)環(huán)境我一般不折騰這層畢竟只是啟動(dòng)那幾秒的事。理解了“depends_on不保證就緒”這個(gè)坑比單純抄配置更值錢。3.3 nginx配置文件fastcgi_pass是關(guān)鍵在conf/nginx/default.conf里寫一套最精簡(jiǎn)的可運(yùn)行配置server { listen 80; server_name localhost; root /var/www/html; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass php:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }listen 80對(duì)應(yīng)容器內(nèi)的80端口宿主機(jī)的8080映射到這里。root /var/www/html必須是兩個(gè)容器共享的那個(gè)目錄在Nginx容器里的路徑。如果這里的路徑和實(shí)際掛載路徑對(duì)不上最常見的表現(xiàn)是訪問首頁出現(xiàn)404而nginx容器里文件確實(shí)存在。最重要的一行是fastcgi_pass php:9000。這個(gè)php不是域名是compose里的服務(wù)名。Compose會(huì)在默認(rèn)網(wǎng)絡(luò)里為每個(gè)服務(wù)生成一個(gè)DNS記錄所以Nginx容器可以直接用服務(wù)名訪問PHP容器。如果不用compose、手動(dòng)用docker run這里就要寫成容器的IP或者加了--link的別名比較麻煩這也是我推薦compose的原因之一。SCRIPT_FILENAME這個(gè)參數(shù)也很關(guān)鍵。Nginx要把請(qǐng)求轉(zhuǎn)發(fā)給PHP-FPM時(shí)需要告訴PHP-FPM“你要解析的文件完整路徑是什么”。$document_root$fastcgi_script_name組合起來就是容器內(nèi)的/var/www/html/index.php。以前常見錯(cuò)誤是只寫$fastcgi_script_name結(jié)果路徑不完整PHP-FPM找不到文件返回404或白屏。3.4 PHP容器加擴(kuò)展一個(gè)簡(jiǎn)單的Dockerfile直接用官方php:8.2-fpm-alpine鏡像能跑純PHP但真實(shí)項(xiàng)目幾乎都要接MySQL、連Redis至少PDO擴(kuò)展是少不了的。這時(shí)候最好的做法不是手動(dòng)進(jìn)容器里裝而是寫一個(gè)Dockerfile把擴(kuò)展固化下來。在項(xiàng)目目錄下建docker/php/DockerfileFROM php:8.2-fpm-alpine RUN apk add --no-cache tzdata \ docker-php-ext-install pdo_mysql mysqli ENV TZAsia/Shanghai然后用build替代imagephp: build: ./docker/php container_name: web-php volumes: - ./www:/var/www/html networks: - webnetdocker-php-ext-install是PHP官方鏡像里自帶的腳本專門用來編譯安裝擴(kuò)展。apk add tzdata是為了處理容器時(shí)區(qū)問題默認(rèn)容器是UTC時(shí)間如果PHP業(yè)務(wù)里用到date()函數(shù)會(huì)發(fā)現(xiàn)比北京時(shí)間少8小時(shí)。加了ENV TZAsia/Shanghai后新起的PHP進(jìn)程就會(huì)讀取這個(gè)時(shí)區(qū)。注意改了Dockerfile后要重新構(gòu)建docker compose build php docker compose up -d如果只是想在本地快速驗(yàn)證不接數(shù)據(jù)庫也可以完全跳過Dockerfile直接用官方鏡像跑。擴(kuò)展方案遇到再說不要一步到位把配置搞復(fù)雜。4. 從啟動(dòng)到驗(yàn)證完整走一遍4.1 啟動(dòng)容器并確認(rèn)狀態(tài)配置都寫好后啟動(dòng)就特別簡(jiǎn)單cd web docker compose up -d docker compose psup -d會(huì)在后臺(tái)啟動(dòng)所有服務(wù)。docker compose ps能看到容器運(yùn)行狀態(tài)如果STATUS是Up說明啟動(dòng)了。Nginx容器會(huì)顯示端口映射0.0.0.0:8080-80/tcp。這個(gè)輸出信息很有用后面排查有沒有綁定成功都靠它。然后訪問首頁curl http://localhost:8080如果返回了我們寫的hello, nginx php in docker說明整個(gè)鏈路已經(jīng)通了。用瀏覽器打開也一樣。第一次跑通的時(shí)候會(huì)有點(diǎn)成就感因?yàn)檫@中間Nginx、PHP-FPM、容器網(wǎng)絡(luò)三個(gè)環(huán)節(jié)任何一個(gè)出錯(cuò)結(jié)果都不是這個(gè)。4.2 如果出現(xiàn)502按這個(gè)順序排查本地搭建最常見的錯(cuò)誤就是502。出現(xiàn)502時(shí)先從日志看docker compose logs nginx日志里如果出現(xiàn)connect() failed (111: Connection refused) while connecting to upstream說明Nginx容器連接php:9000失敗。接下來依次確認(rèn)三件事一PHP容器是否在運(yùn)行docker compose ps看到web-php狀態(tài)二compose網(wǎng)絡(luò)里服務(wù)名是不是php和fastcgi_pass php:9000是否一致三PHP-FPM到底有沒有監(jiān)聽9000端口。第三點(diǎn)很多人會(huì)忽略可以在PHP容器里直接驗(yàn)證docker compose exec php sh -c netstat -tuln | grep 9000有些精簡(jiǎn)鏡像可能沒裝netstat也可以用ss -tuln或者php-fpm -t測(cè)試配置語法。如果9000端口沒有監(jiān)聽多半是PHP-FPM啟動(dòng)失敗這時(shí)看PHP容器日志docker compose logs php日志里會(huì)給出具體錯(cuò)誤比如配置文件路徑不對(duì)、用戶不存在等。按提示處理即可。4.3 如果出現(xiàn)404或直接下載PHP文件404通常不是網(wǎng)絡(luò)問題而是文件路徑問題。確認(rèn)一下Nginx容器里能不能看到代碼文件docker compose exec nginx ls -l /var/www/html如果看不到index.php說明掛載有問題重新檢查compose里的volumes映射。如果能看到文件但還是404重點(diǎn)看root路徑和index指令沒有index index.php時(shí)Nginx不會(huì)自動(dòng)把請(qǐng)求交給PHP索引文件。還有一種現(xiàn)象是瀏覽器直接下載.php文件而不是解析這說明Nginx根本沒用location ~ \.php$那段配置處理PHP請(qǐng)求請(qǐng)求被當(dāng)成普通靜態(tài)文件返回了。檢查一下掛載的配置文件是否真的生效可以在Nginx容器里docker compose exec nginx cat /etc/nginx/conf.d/default.conf如果看到的內(nèi)容和你本地的default.conf不一致說明容器里的配置文件沒有更新需要重啟Nginx容器。4.4 用phpinfo驗(yàn)證環(huán)境細(xì)節(jié)能訪問到PHP內(nèi)容后可以把index.php臨時(shí)改成?php phpinfo();再刷新頁面就能看到PHP版本、配置項(xiàng)、已加載模塊等一整套信息。重點(diǎn)看兩個(gè)地方PHP Version確認(rèn)版本和鏡像里一致Server API顯示FPM/FastCGI說明PHP確實(shí)是由PHP-FPM解析的。phpinfo()信息量很大本地調(diào)試用起來特別方便但生產(chǎn)環(huán)境絕對(duì)不要暴露給外部訪問這點(diǎn)要養(yǎng)成習(xí)慣。想看PHP擴(kuò)展也可以直接進(jìn)容器docker compose exec php php -m這個(gè)命令會(huì)列出所有已安裝模塊比如PDO、pdo_mysql、mysqli如果裝了就都會(huì)在里面。整個(gè)排查鏈路走下來基本覆蓋了這類組合環(huán)境90%的啟動(dòng)問題。5. 常見問題與避坑速查整理了一份常見問題表按現(xiàn)象直接查比反復(fù)看日志省時(shí)間現(xiàn)象可能原因處理方式啟動(dòng)報(bào)端口被占用8080被宿主機(jī)其他進(jìn)程占用改compose里8080:80為8081:80502 Bad GatewayNginx連不上php:9000檢查PHP容器狀態(tài)、服務(wù)名、9000端口監(jiān)聽404 Not Found根路徑或文件名不匹配檢查root路徑、掛載和index指令PHP文件被下載沒有走location ~ \.php$檢查nginx配置是否生效修改代碼后不生效本地文件和容器內(nèi)路徑不一致docker compose exec nginx ls確認(rèn)掛載時(shí)區(qū)差8小時(shí)容器默認(rèn)UTC在Dockerfile裝tzdata并設(shè)置TZ5.1 端口被占用別硬剛換個(gè)端口macOS上“端口被占用”幾乎是必踩的坑。我遇到過好多次不是系統(tǒng)服務(wù)占用了80就是別的項(xiàng)目占用了8080。改端口不是丟人的事本地開發(fā)本來就該避開常用端口。把compose里的ports改成8081:80再docker compose up -d重建就好了。不用改Nginx里的listen 80那是容器內(nèi)部端口宿主機(jī)端口只由ports這行控制。5.2 docker compose down到底刪了什么docker compose down會(huì)停止并刪除容器但不會(huì)被刪除掛載的宿主機(jī)目錄所以代碼文件不會(huì)丟。加了-v之后會(huì)連匿名卷一起刪如果數(shù)據(jù)放在命名卷或宿主機(jī)目錄里一般也不受影響。但我在早期不熟悉時(shí)有一次執(zhí)行down -v把數(shù)據(jù)庫容器里的數(shù)據(jù)卷刪了當(dāng)場(chǎng)懵掉。這里我的建議是本地項(xiàng)目里凡是需要持久化的數(shù)據(jù)都明確掛載到宿主機(jī)目錄比如./data:/var/lib/mysql永遠(yuǎn)不要把數(shù)據(jù)只放在容器里。容器是隨時(shí)可以重建的數(shù)據(jù)不是。5.3 Docker Desktop資源占用和清理Docker Desktop默認(rèn)分配的資源和本機(jī)配置有關(guān)跑兩個(gè)容器可能很輕但如果同時(shí)開很多容器會(huì)吃內(nèi)存。打開Docker Desktop設(shè)置可以調(diào)高CPU和內(nèi)存限制調(diào)完需要重啟Docker服務(wù)。另外鏡像越拉越多磁盤會(huì)被撐滿。清理時(shí)我用docker system prune清理懸空鏡像、停止的容器和無用資源會(huì)提示確認(rèn)。這個(gè)命令會(huì)刪除停止的容器和懸空的鏡像謹(jǐn)慎使用但磁盤吃緊時(shí)真的管用。6. 一點(diǎn)配置心得和后續(xù)擴(kuò)展6.1 為什么這套組合適合本地開發(fā)很多人覺得在本地裝Docker麻煩不如直接裝個(gè)Nginx、PHP。我個(gè)人的體會(huì)是Docker方案最大的價(jià)值不在“安裝”而在“一致性”。一份docker-compose.yml放進(jìn)項(xiàng)目倉庫不管換電腦還是換同事只要裝了Docker一條命令就能復(fù)現(xiàn)同一個(gè)環(huán)境。再也不用為“我本機(jī)是PHP 7.4你本機(jī)是8.2跑出來結(jié)果不一樣”這種問題浪費(fèi)時(shí)間。如果你經(jīng)常做PHP相關(guān)開發(fā)這幾乎是最省心的本地環(huán)境方案。6.2 在這個(gè)基礎(chǔ)上還能加什么跑通NginxPHP只是第一步。后續(xù)最自然的擴(kuò)展是加數(shù)據(jù)庫和緩存服務(wù)在compose里再加一個(gè)mysql和redis服務(wù)PHP容器通過安裝對(duì)應(yīng)擴(kuò)展連接它們整個(gè)架構(gòu)就和真實(shí)項(xiàng)目非常接近了。再往后可以把PHP項(xiàng)目的代碼通過Dockerfile直接打包進(jìn)鏡像徹底擺脫源碼目錄掛載變成一套可交付的環(huán)境。舉個(gè)例子加MySQL服務(wù)時(shí)compose里新增一個(gè)服務(wù)指定鏡像、環(huán)境變量、數(shù)據(jù)卷再把PHP容器掛到同一個(gè)webnet網(wǎng)絡(luò)里。PHP代碼里連接數(shù)據(jù)庫的主機(jī)地址直接寫服務(wù)名而不是127.0.0.1因?yàn)槿萜髦g不能通過localhost互相訪問。這類擴(kuò)展并不難但需要理解容器網(wǎng)絡(luò)的基本邏輯。最后分享一個(gè)我自己的小習(xí)慣每配置完一個(gè)服務(wù)都會(huì)單獨(dú)驗(yàn)證這個(gè)服務(wù)是否可用而不是等所有配置寫完才啟動(dòng)。比如先只啟動(dòng)PHP容器確認(rèn)php -m正常再啟動(dòng)Nginx容器確認(rèn)靜態(tài)文件可訪問最后把兩者連起來驗(yàn)證動(dòng)態(tài)解析。分段驗(yàn)證能讓問題在最簡(jiǎn)單的上下文里暴露排查起來輕松很多。這套組合跑通之后后面不管加MySQL還是Redis我心里都有底了。