
不知道你有沒有遇到過這種場景接手一個三年前的老項目文檔上寫著“請使用 Node 16 npm 8 啟動”而你本機裝的是 Node 22。跑起來報錯降級又怕影響手頭其他項目。這時候你心里大概率會冒出一句“我電腦能跑你電腦怎么就跑不起來”這不是玄學這是環(huán)境依賴在作祟。今天的文章就用一個最小實戰(zhàn)帶你用 Docker 和 Nginx 反向代理把“能跑”這件事從個人電腦上拆下來變成一份可以分發(fā)的配置。一、Docker 到底解決了什么問題先拋開那些厚重的概念記住一個類比Docker 應用 運行環(huán)境就像海運的集裝箱你把貨物應用和必要的保護措施運行環(huán)境打包進一個標準箱子然后這艘“萬噸巨輪”可以把這個箱子運到任何港口。無論目的港是深圳、鹿特丹還是洛杉磯開箱即用。放到開發(fā)里也一樣。一個完整的 Web 應用往往不止有代碼還有Node.js 的版本npm/yarn 的版本Redis、MySQL 等中間件各種系統(tǒng)級依賴任何一個環(huán)節(jié)不一致都可能導致“你電腦能跑我電腦不能跑”。Docker 做的就是把這些依賴連同代碼一起打成一個鏡像然后在你任何設備上以容器的形式運行起來。如果你關注 AI Agent會發(fā)現(xiàn)一個有趣的類比Agent LLM Harnesstool mcp rag skill...Docker 應用 運行環(huán)境本質(zhì)上它們都是在解決“能力 環(huán)境”的組合問題。兩個必須理解的概念Image 和 ContainerImage鏡像類似于一張光盤。它包含了應用程序和它需要的環(huán)境是只讀的模板。Container容器類似于光盤放進 DVD 播放器后正在播放的內(nèi)容。它是鏡像運行起來的實例可讀可寫。所以你會發(fā)現(xiàn)Docker 的日常操作基本就是圍繞這兩個東西展開docker pull nginx # 拉取鏡像相當于下載光盤 docker run nginx # 運行鏡像相當于把光盤放進播放器 docker stop xxx # 停止容器 docker rm xxx # 刪除容器 docker rmi nginx # 刪除鏡像先記住這兩組概念后面實戰(zhàn)中你會反復看到它們。二、實戰(zhàn)用 Nginx 反向代理一個 Node 服務假設我們現(xiàn)在有一個最簡單的 Node 服務// index.js const http require(http) const server http.createServer((req, res) { res.end(hello world) }) server.listen(1314, 0.0.0.0, () { console.log(node service run on 1314) })這個服務監(jiān)聽在1314端口注意這里監(jiān)聽的是0.0.0.0而不是127.0.0.1。為什么要這樣后面你會看到因為 Nginx 容器需要通過宿主機訪問這個服務如果只監(jiān)聽回環(huán)地址容器可能無法訪問?,F(xiàn)在你在本機啟動它node index.js瀏覽器訪問http://localhost:1314會看到熟悉的hello world。但問題來了用戶會輸入localhost:1314嗎不會。用戶只會輸入http://localhost甚至是一個域名。而 HTTP 協(xié)議默認端口是80。也就是說用戶永遠只會從 80 端口進來但你的服務跑在 1314 端口。這就需要一位“前臺接待員”——Nginx。為什么需要 Nginx因為在實際生產(chǎn)環(huán)境中你需要一個統(tǒng)一入口來處理請求用戶訪問 80 端口Nginx 監(jiān)聽 80 端口Nginx 根據(jù)配置把請求轉(zhuǎn)發(fā)到真正運行服務的 1314 端口這就是反向代理。說白了就是用戶不知道你的服務在哪個端口也不該知道Nginx 幫你擋在前面。準備 Nginx 配置文件# nginx.conf events {} http { server { listen 80; location / { proxy_pass http://host.docker.internal:1314; proxy_set_header Host $host; } } }這里有一個關鍵點host.docker.internal。這是 Docker Desktop 提供的一個特殊 DNS 名稱它指向宿主機。因為我們的 Node 服務直接跑在宿主機上而 Nginx 跑在容器里所以 Nginx 需要通過這個地址訪問宿主機的 1314 端口。用 Docker 啟動 Nginxdocker run --name my-nginx-demo \ -p 80:80 \ -v /path/to/nginx.conf:/etc/nginx/nginx.conf \ -d nginx這條命令拆開來看就是 Docker 的日常操作核心--name my-nginx-demo給容器起個名字方便后續(xù)管理。-p 80:80端口映射本機的 80 端口映射到容器的 80 端口。-v /path/to/nginx.conf:/etc/nginx/nginx.conf把本機的配置文件掛載到容器內(nèi) Nginx 的默認配置路徑。-d nginx后臺運行nginx鏡像。如果不設置會阻塞運行需要重開一個進程。現(xiàn)在瀏覽器訪問http://localhost請求會先到本機 80 端口然后通過 Docker 端口映射進入 Nginx 容器再根據(jù)配置文件代理到宿主機的 1314 端口最終返回hello world。三、請求鏈路拆解一次請求的完整旅程這個例子雖然簡單但把 Docker 和 Nginx 反向代理的核心鏈路全串起來了。你可以把下面這條鏈路在腦子里過一遍瀏覽器 http://localhost:80 ↓ 宿主機 80 端口 ↓ Docker 端口映射 -p 80:80 ↓ Nginx 容器內(nèi) 80 端口 ↓ nginx.conf 反向代理規(guī)則 ↓ host.docker.internal:1314宿主機 Node 服務 ↓ 返回 hello world你會發(fā)現(xiàn)整個過程中用戶完全不知道服務真正跑在 1314 端口。對于用戶來說只有一個入口http://localhost。這就是反向代理和正向代理的區(qū)別正向代理代理的是客戶端比如你掛了個代理去訪問外網(wǎng)服務器不知道你是誰。反向代理代理的是服務端比如 Nginx 擋在服務前面用戶不知道真正的服務在哪。反向代理的意義不只是端口轉(zhuǎn)發(fā)它把“服務真正在哪里”這件事藏了起來對外只暴露一個干凈的入口。四、踩坑點與解決方案在實際操作中有幾個點需要特別注意不然很容易卡住。1.host.docker.internal的兼容性問題host.docker.internal是 Docker DesktopWindows/Mac自帶的特性。如果你在 Linux 服務器上運行 Docker這個 DNS 名稱可能不存在。解決方案是添加--add-host參數(shù)docker run --name my-nginx-demo \ -p 80:80 \ -v /path/to/nginx.conf:/etc/nginx/nginx.conf \ --add-hosthost.docker.internal:host-gateway \ -d nginxhost-gateway會讓 Docker 自動解析到宿主機 IP這樣 Nginx 就能訪問宿主機的 Node 服務了。2. 端口映射的順序-p 80:80里面第一個 80 是宿主機端口第二個 80 是容器端口。這個順序別搞反了。如果你本機 80 端口已經(jīng)被占用比如本機已經(jīng)有 Nginx 在跑可以改成-p 8080:80然后訪問http://localhost:8080。3. 掛載路徑的格式在 Windows 上掛載路徑可能需要寫成E:\workspace...但更推薦使用絕對路徑或者 Docker 的 volume。上面的示例中我用了/path/to/nginx.conf你需要替換成自己本機的實際路徑。4. 容器清理如果折騰了一堆容器想快速清理環(huán)境可以用以下命令# 停止所有運行中的容器 docker stop $(docker ps -q) # 刪除所有容器 docker rm $(docker ps -aq) # 刪除 nginx 鏡像 docker rmi nginx注意docker rm只能刪除已停止的容器如果容器還在運行需要先執(zhí)行docker stop或者直接用docker rm -f強制刪除。五、擴展用 Docker 跑一個 MySQLDocker 的便利性在數(shù)據(jù)庫這種強環(huán)境依賴的場景下體現(xiàn)得更加明顯。比如你本機已經(jīng)裝了一個 MySQL 5.7但項目要求 MySQL 8.0你不可能卸載重裝。這時候 Docker 就派上用場了。# 拉取 MySQL 8.0 鏡像 docker pull mysql:8.0 # 運行 MySQL 容器映射到宿主機 3307 端口 docker run -d --name mysql-demo \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ mysql:8.0這里有兩個點需要注意-p 3307:3306MySQL 默認監(jiān)聽 3306 端口但如果本機已有 MySQL 占用了 3306就映射到 3307避免沖突。-e MYSQL_ROOT_PASSWORD123456通過環(huán)境變量設置 MySQL 的 root 密碼-e就是--env的縮寫。進入容器內(nèi)部可以像操作普通 Linux 一樣操作docker exec -it mysql-demo /bin/bash mysql -uroot -p123456docker exec -it是進入一個正在運行的容器并打開交互式終端之后你就可以在容器內(nèi)部執(zhí)行命令了。-i--interactive保持輸入流開啟能接收鍵盤輸入-t--tty分配虛擬終端顯示命令行提示符-it幾乎永遠成對使用用來做交互式操作六、總結(jié)Docker 的核心就三件事回顧一下這篇文章的內(nèi)容你會發(fā)現(xiàn) Docker 的核心其實就是三件事打包把應用和運行環(huán)境打包成鏡像。分發(fā)鏡像可以輕松 pull/push任何設備都能拿到同一份環(huán)境。隔離容器之間互不影響不會因為版本沖突而崩潰。而 Nginx 反向代理則是把“用戶入口”和“服務端口”解耦讓你可以靈活地在后面掛任何服務。環(huán)境問題不再是玄學容器化讓你把“能跑”這件事固化下來變成一份可移植的配置。下次再遇到“我電腦能跑你電腦跑不起來”的情況別急著懷疑人生先問問自己這件事是不是該交給 Docker 了