前面我們已經完成:
Ubuntu Server
│
├── Nginx
│
└── systemd
│
▼
FastAPI
目前 FastAPI 的執行環境是直接安裝在 Server 裡面。
例如:
Ubuntu
├── Python 3.12
├── virtualenv
├── FastAPI
├── Uvicorn
└── Application Code
啟動則交給:
systemd
這種方式本身沒有問題。
但接下來我們想把:
Python
Dependencies
Application Code
Startup Command
包成一個可以重複使用的:
Docker Image
前面的 FastAPI 專案:
fastapi-deployment-lab/
│
├── app/
│ ├── __init__.py
│ └── main.py
│
├── requirements.txt
└── .venv/
其中 app/main.py:
from fastapi import FastAPI
app = FastAPI(
title="FastAPI Deployment Lab",
)
@app.get("/")
async def root():
return {
"message": "Hello FastAPI"
}
@app.get("/health")
async def health():
return {
"status": "ok"
}
requirements.txt 至少包含:
fastapi
uvicorn
在專案根目錄建立:
Dockerfile
專案變成:
fastapi-deployment-lab/
│
├── app/
│ ├── __init__.py
│ └── main.py
│
├── requirements.txt
└── Dockerfile
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install \
--no-cache-dir \
-r requirements.txt
COPY app ./app
CMD [
"uvicorn",
"app.main:app",
"--host",
"0.0.0.0",
"--port",
"8000"
]
接下來逐段理解這份 Dockerfile。
FROM python:3.12-slim
意思是:
這個 Application 的基礎環境使用 Python 3.12。
以前我們要在 Server 上手動:
sudo apt install python3
現在 Python Runtime 已經包含在:
python:3.12-slim
這個 Base Image 裡。
因此:
Server A
Server B
Server C
只要跑同一份 Image,就不需要分別確認:
Server A Python 3.12?
Server B Python 3.11?
Server C Python 3.10?
Application 的 Runtime 已經被固定下來。
WORKDIR /app
代表:
Container 裡面的工作目錄
=
/app
後面的:
COPY
RUN
CMD
主要都以:
/app
作為工作位置。
COPY requirements.txt .
會把本機:
requirements.txt
複製進 Container Image:
/app/requirements.txt
此時 Image 內概念上:
/app/
└── requirements.txt
接著:
RUN pip install \
--no-cache-dir \
-r requirements.txt
會在 Build Image 的過程安裝:
FastAPI
Uvicorn
其他 Python dependencies
所以以前 Server 上需要:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
現在這件事情在:
docker build
時完成。
傳統 Server:
Ubuntu
│
├── System Python
│
└── .venv
├── FastAPI
└── Uvicorn
使用 virtualenv 是為了避免:
Application A 的 Package
跟
Application B 的 Package
互相干擾。
但是 Container 本身就已經提供了一層隔離:
Container A
├── Python
└── Packages
Container B
├── Python
└── Packages
所以很多 Docker Image 會直接:
pip install
而不另外建立:
.venv
這並不是說 virtualenv 不能放在 Docker 裡,而是通常沒有必要再增加一層隔離。
COPY app ./app
把 FastAPI 程式:
app/
複製進 Image。
Image 內容現在大約:
/app/
│
├── requirements.txt
│
└── app/
├── __init__.py
└── main.py
CMD [
"uvicorn",
"app.main:app",
"--host",
"0.0.0.0",
"--port",
"8000"
]
意思是:
Container 啟動時,自動啟動 FastAPI。
以前 systemd:
ExecStart=/var/www/.../.venv/bin/uvicorn \
app.main:app \
--host 127.0.0.1 \
--port 8000
現在啟動指令被放進:
Docker Image
裡。
所以 Container 啟動時就知道:
我要跑什麼程式
這裡跟前面的 systemd 版本有一個重要差別。
之前:
FastAPI
127.0.0.1:8000
因為 Nginx 和 FastAPI 都在同一台 Host。
但 Container 有自己的 Network Namespace。
如果 FastAPI 在 Container 裡只綁:
127.0.0.1
那通常只代表:
Container 自己的 localhost
Host 很可能無法透過 Container Port 正常存取。
因此 Container 裡通常使用:
0.0.0.0:8000
表示:
監聽 Container 裡所有 Network Interface
再由 Docker 決定要不要把這個 Port 暴露到 Host。
建議同時建立:
.dockerignore
例如:
.venv
__pycache__
*.pyc
.git
.env
.idea
.DS_Store
避免:
virtualenv
Git History
IDE 設定
環境變數檔
全部被送進 Docker Build Context。
專案現在:
fastapi-deployment-lab/
│
├── app/
├── requirements.txt
├── Dockerfile
└── .dockerignore
執行:
docker build \
-t fastapi-deployment-lab:v1 .
其中:
docker build
代表:
根據 Dockerfile 建立 Image
-t:
幫 Image 命名
所以最後得到:
fastapi-deployment-lab:v1
可以查看:
docker images
可能看到:
REPOSITORY TAG
fastapi-deployment-lab v1
這個 Image 現在包含:
Python 3.12
+
Python Packages
+
FastAPI Code
+
Startup Command
接著:
docker run \
--name fastapi-demo \
-p 8000:8000 \
fastapi-deployment-lab:v1
現在:
Docker Host
│
│ :8000
▼
Container
│
│ :8000
▼
Uvicorn
│
▼
FastAPI
測試:
curl http://127.0.0.1:8000/
應該看到:
{
"message": "Hello FastAPI"
}
Health Check:
curl http://127.0.0.1:8000/health
得到:
{
"status": "ok"
}
這一段:
-p 8000:8000
可以理解成:
Host Port
8000
↓
Container Port
8000
所以:
127.0.0.1:8000
會被 Docker 導向:
Container:8000
如果寫:
-p 9000:8000
則:
Host
9000
↓
Container
8000
此時要:
curl http://127.0.0.1:9000
但 FastAPI 在 Container 裡仍然是:
8000
剛才:
docker run ...
會佔著 Terminal。
正式使用時可以:
docker run \
-d \
--name fastapi-demo \
-p 8000:8000 \
fastapi-deployment-lab:v1
-d:
Detached Mode
也就是背景執行。
查看:
docker ps
Log:
docker logs fastapi-demo
即時 Log:
docker logs -f fastapi-demo