iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0
IT Operation

從前後端踏上 AWS 雲端架構勇者之路系列 第 21 篇

Docker Image 是什麼?把程式打包後,再交給主機執行

  • 分享至 

  • xImage
  •  

假設你寫好一支 Node.js 程式,在自己的電腦執行沒問題。交給同事後,他卻說跑不起來

你用 Node.js 24,他可能還在用舊版。你電腦裡裝好的套件,他那邊也不一定有。明明傳的是同一份程式,怎麼還要花時間對環境?

這就接到今天的 Docker Image(容器映像檔)。我們把程式、套件和需要的執行環境一起打包,交給另一台主機的 Docker 啟動,減少每台電腦各裝各的差異

Image 和 Container 差在哪?

Image 是打包好的內容,還沒有在替使用者處理請求。用這份 Image 啟動後,正在執行的那一份,才叫 Container(容器)

看下面這張圖,同一份 Image 可以啟動兩個 Container。假設你把其中一個停掉,另一個仍能繼續跑,原本的 Image 也還在

所以看到 Image 列表裡有 demo-api,只能說這份程式已經打包好;要找正在跑的那一份,得看 Container

程式碼與 Node.js 經 docker build 建成 Image,同一份 Image 可啟動多個 Container

這也解釋了為什麼部署時會分成 build 和 run:前者是打包,後者才是把程式跑起來。Image 不是正在運作的網站,跟把 ZIP 上傳到硬碟一樣,放好了不代表程式自己會執行,也附上(Docker 的 Image 與 Container 的差異)

那我平常的 npm run build 呢?

如果你平常用 npm run build 打包前端,看到這裡可能會想:現在要改用 Docker,原本那個指令還要跑嗎?

先看 package.json 裡的 scripts.build 寫了什麼。npm run build 會執行你設定的指令;例如 Vite 專案常見的是 vite build,把前端程式建置成 dist 裡的檔案(也附上 npm run 的用途)

docker build 則是照 Dockerfile,把要交給主機的檔案與執行環境做成 Image。所以原本的專案需要編譯,這一步仍然要做,也可以安排在 Dockerfile 裡執行 RUN npm run build

習慣用 npm 管理指令也可以。假設你把 docker build -t demo-api:v1 . 設成 scripts 裡的 docker:build,就能用 npm run docker:build 呼叫它。實際做的事相同,只是入口換成你熟悉的 npm

這次的小程式直接由 Node.js 執行,我們先用 Docker 指令看清楚整個流程

先用很小的程式看一次

我們先不放完整網站,只做一個能回應 HTTP 的 Node.js 範例。電腦要先有 Docker,並確認 docker version 看得到 Client 和 Server;只有指令裝好了,Docker 引擎沒啟動,一樣不能執行容器

Windows 可以安裝 Docker Desktop(Windows),依官方要求準備 WSL 2,使用 Linux containers;Mac 則依 Intel 或 Apple Silicon 選 對應安裝版

這篇都在自己的電腦操作,還不用 SSH 到 EC2。在放練習專案的位置執行:

mkdir demo-api
cd demo-api

用 VS Code 開啟這個資料夾,新增 server.cjs:

const http = require('node:http');

http.createServer((req, res) => {
  const ok = req.method === 'GET' && req.url === '/health';
  res.writeHead(ok ? 200 : 404, {
    'Content-Type': 'application/json; charset=utf-8',
  });
  res.end(JSON.stringify(ok ? { status: 'ok' } : { error: 'not found' }));
}).listen(8080, '0.0.0.0', () => console.log('API listening on 8080'));

/health 是我們自己寫的路徑,回應 ok 表示這支程式有在處理請求。Docker 不會自動替每個網站加上這個功能

接著在 server.cjs 旁邊新增 Dockerfile,檔名就叫這個,不要加 .txt。它是給 Docker 看的打包步驟,內容如下:

FROM node:24-alpine
WORKDIR /app
COPY server.cjs ./
USER node
EXPOSE 8080
CMD ["node", "server.cjs"]

第一行從已經有 Node.js 的基底 Image 開始,接著設定工作目錄、複製程式,最後指定啟動指令。這支程式只用 Node.js 內建功能,所以不需要 npm install

USER node 是讓應用程式用 Image 裡的 node 帳號執行,不必為了回應 HTTP,給程式容器內的 root 身分

同個資料夾再加 .dockerignore:

.env
.env.*
*.pem
.git
node_modules

這是在限制哪些檔案不要送進打包範圍。尤其改成 COPY . . 時,沒排除 .env,很容易連資料庫密碼一起包進去。就算後來在 Dockerfile 刪掉,也不能當作前面那一層的機密已經消失

怎麼從 Image 變成可以測的 API?

現在 demo-api 裡應有三個檔案:server.cjs、Dockerfile、.dockerignore。它們放同一層,再回終端機執行下面三步:先打包、啟動,最後送一次測試請求

每一步沒有出錯,再執行下一步:

docker build -t demo-api:v1 .
docker run --rm -d --name demo-api -p 127.0.0.1:8080:8080 demo-api:v1
curl -i http://127.0.0.1:8080/health

Windows PowerShell 的測試指令請使用 curl.exe -i http://127.0.0.1:8080/health;後面遇到 curl 也改用 curl.exe,避免叫到 PowerShell 的同名指令。其餘指令照貼即可

demo-api 是 Image 名稱,v1 是這一版的標籤。最後的 . 表示以目前資料夾作為打包來源,少了它,Docker 不知道要從哪裡讀檔案

run 裡的 --name demo-api 是替正在跑的容器取名,-d 讓它在背景執行,--rm 表示停止後自動移除這個練習容器。curl 則是用指令發出 HTTP 請求,功能像這次在瀏覽器打開測試網址

這裡有兩個 8080。左邊是自己電腦的 port,右邊是 Container 裡程式監聽的 port;加上 127.0.0.1,表示這次只從自己的電腦連入,不直接開給外面的電腦

把電腦和 Container 畫開,就比較好認了。curl 先找到電腦上的 8080,再由 Docker 對到 Container 的 8080。程式在容器內監聽 0.0.0.0,才收得到從容器外送進來的請求

主機的 127.0.0.1:8080 透過 Docker port 對應到容器內的 8080

成功時,HTTP 狀態應是 200,內容是 {"status":"ok"}。再測一次不存在的路徑:

curl -i http://127.0.0.1:8080/missing

這次應回 404 與 {"error":"not found"},不是每個網址都說成功。如果剛剛改用 8081,這裡的網址也要改成 8081

看不到回應時,先用 docker logs demo-api 看程式有沒有啟動。假如剛啟動還沒準備好,等看到 API listening on 8080 後再測一次

改完程式,怎麼讓新版跑起來?

之後若修改 server.cjs,目前的 Container 不會自己更新。先儲存檔案,再回到有 Dockerfile 的 demo-api 資料夾,依序執行;每一步成功才繼續:

docker build -t demo-api:v1 .
docker stop demo-api
docker run --rm -d --name demo-api -p 127.0.0.1:8080:8080 demo-api:v1
curl -i http://127.0.0.1:8080/health

中間的 stop 很重要:舊容器還在執行,就占著 demo-api 這個名稱與電腦的 port。先停掉它,啟動時加的 --rm 會自動移除舊容器,新的那份才能使用同樣的名稱

假如你前面已經停止容器、看到 No such container,表示這個名稱已經空出來,可以接著執行 run

記得前面選的是 8081,這裡也沿用 -p 127.0.0.1:8081:8080 與 8081 的測試網址;已啟動後想換 port,同樣先停掉舊容器再重跑 run

重新 build 會更新 demo-api:v1 指向的 Image,但正在跑的舊容器不會跟著換。這也是為什麼只有 build,還看不到程式更新

打包了,就能在任何主機跑嗎?

還有一個條件要對上:CPU 架構,也就是處理器使用的指令系統。x86_64 和 AMD64 在這裡指同一類 64 位元架構,不是只有 AMD 的電腦才能用

例如 Apple Silicon 和 AWS 的 T4g 都是 ARM64,但一般 Intel/AMD 主機常見的是 AMD64。拿錯架構的 Image,可能直接出現 exec format error。Docker 可以處理多平台建置,但不能只因為副檔名或 Image 名稱一樣,就認為裡面的程式一定適合那台機器

我們後續以 ARM64 的 EC2 為例,打包時就明確指定 linux/arm64。所以如果你的 EC2 是 AMD64,整條流程改成 linux/amd64(Docker 多平台建置)

這樣的話用 Windows 或 Mac 都能跟著做,不用為了 EC2 換一台電腦。下面先畫出這次部署到 T4g 的方向,下一篇會真的帶入建置指令

Windows 與兩種 Mac 都能在本機執行;部署到 T4g 時另外建置 linux/arm64 Image

有個細節要提醒下,儘管你程式包好了,不表示資料庫地址、密碼和 AWS 權限也都準備好了

換一個部署環境,這些設定可能不同,這個細節我們後面會在啟動 Container 時另外交給它~

今天學了瞎咪

動作 得到什麼
docker build 一份可以交給主機使用的 Image
docker run 一個正在執行的 Container
呼叫 /health 確認程式真的有回應 HTTP

練完執行 docker stop demo-api。剛剛啟動時有加 --rm,所以 Container 停止後會移除,Image 則留著

下一篇我們就來學 ECR,讓 AWS 上的 EC2 也找得到,也下載得下來,我們明天見勒 :D


上一篇
SES 郵件服務(下):用 Node.js 寄一封信給自己
下一篇
ECR:把本機的 Docker Image 推上 AWS
系列文
從前後端踏上 AWS 雲端架構勇者之路 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言