0. 前提:安装 Docker(若已安装可跳过)
bash
# CentOS 7/8/Stream 通用
sudo yum -y install yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum -y install docker-ce docker-ce-cli containerd.io
sudo systemctl enable --now docker
docker version
如果服务器启用了防火墙或 SELinux,本文下面会给出对应处理方式。
1. 准备项目文件
在服务器新建目录并进入:
bash
mkdir -p ~/fastapi-upload && cd ~/fastapi-upload
1.1 main.py
python
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
import os
from datetime import datetime
app = FastAPI(title="FastAPI Upload Service")
UPLOAD_DIR = "uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)
app.mount("/files", StaticFiles(directory=UPLOAD_DIR), name="files")
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
ts = datetime.now().strftime("%Y%m%d%H%M%S")
safe_name = file.filename.replace("/", "_").replace("\\", "_")
filename = f"{ts}_{safe_name}"
path = os.path.join(UPLOAD_DIR, filename)
with open(path, "wb") as f:
f.write(await file.read())
# 返回相对路径,前端/调用方拼上域名即可访问下载
return JSONResponse({"filename": filename, "url": f"/files/{filename}"})
1.2 requirements.txt
fastapi>=0.110
uvicorn[standard]>=0.29
1.3 Dockerfile
dockerfile
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt && \
pip install --no-cache-dir gunicorn
COPY main.py ./main.py
# 非 root 运行更安全
RUN useradd -m appuser && mkdir -p /app/uploads && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
# 健康检查(可选)
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD python - <<'PY'\nimport urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/docs').read()\nPY
# 生产推荐:Gunicorn 托管 Uvicorn Worker
CMD ["gunicorn", "-w", "2", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000", "main:app"]
2. 构建镜像
bash
cd ~/fastapi-upload
docker build -t fastapi-upload:latest .
3. 准备持久化目录
bash
sudo mkdir -p /data/fastapi-uploads
sudo chown -R 1000:1000 /data/fastapi-uploads # 1000 是 appuser 的常见 uid,如需可改
SELinux 开启时 挂载建议使用
:Z
选项(下文已包含)。
4. 启动容器(长期运行)
bash
docker run -d \
--name fastapi-upload \
-p 8000:8000 \
-v /data/fastapi-uploads:/app/uploads:Z \
--restart=always \
fastapi-upload:latest
-d
:后台运行--restart=always
:宕机/重启均自动拉起-v
:将上传目录持久化在宿主机(数据安全)
查看状态与日志:
bash
docker ps -a
docker logs -f fastapi-upload
5. 防火墙与 SELinux
5.1 防火墙放行端口(例:8000)
bash
sudo firewall-cmd --add-port=8000/tcp --permanent
sudo firewall-cmd --reload
5.2 SELinux(如开启)
- 我们在挂载时用了
:Z
,这会自动做上下文标记,通常即可。 - 如仍遇到 Nginx/HTTP 无法转发到容器的 SELinux 限制,再针对具体服务设置布尔值。
6. 验证与调用
6.1 打开文档
浏览器访问:http://<服务器IP>:8000/docs
6.2 Postman(或 curl)上传
- POST
http://<服务器IP>:8000/upload
- Body →
form-data
:键名file
,类型 File,选一个本地文件
curl 示例:
bash
curl -X POST "http://<服务器IP>:8000/upload" \
-F "file=@/path/to/local/test.png"
返回示例:
json
{"filename":"20251009_test.png","url":"/files/20251009_test.png"}
6.3 下载
直接访问:http://<服务器IP>:8000/files/20251009_test.png
7. 一键脚本(可选,复制即用)
保存为 deploy.sh
:
bash
#!/usr/bin/env bash
set -e
APP_DIR=~/fastapi-upload
DATA_DIR=/data/fastapi-uploads
IMAGE=fastapi-upload:latest
NAME=fastapi-upload
PORT=8000
# 准备目录
mkdir -p "$APP_DIR"
sudo mkdir -p "$DATA_DIR"
sudo chown -R 1000:1000 "$DATA_DIR"
# 构建镜像
cd "$APP_DIR"
docker build -t "$IMAGE" .
# 运行容器(若已存在则替换)
if docker ps -a --format '{{.Names}}' | grep -w "$NAME" >/dev/null; then
docker rm -f "$NAME"
fi
docker run -d \
--name "$NAME" \
-p ${PORT}:8000 \
-v ${DATA_DIR}:/app/uploads:Z \
--restart=always \
"$IMAGE"
echo "==> Service is up: http://<SERVER-IP>:${PORT}/docs"
执行:
bash
chmod +x deploy.sh
./deploy.sh
8. 更新/升级(零停机思路 & 简单思路)
简单思路(短暂中断几秒):
bash
# 修改代码后重新构建镜像
docker build -t fastapi-upload:latest .
# 重启容器
docker rm -f fastapi-upload
docker run -d --name fastapi-upload \
-p 8000:8000 \
-v /data/fastapi-uploads:/app/uploads:Z \
--restart=always \
fastapi-upload:latest
近似零停机(平滑切换端口):
-
启新容器占用备用端口(如 8001):
bashdocker run -d --name fastapi-upload-v2 \ -p 8001:8000 \ -v /data/fastapi-uploads:/app/uploads:Z \ --restart=always \ fastapi-upload:latest
-
(若有 Nginx 反代)把 Nginx 的 upstream 从 8000 切到 8001,
nginx -s reload
。 -
确认新容器稳定后,删除旧容器:
bashdocker rm -f fastapi-upload docker rename fastapi-upload-v2 fastapi-upload
9. 常见问题
- 上传失败/下载 404 :确认
app.mount("/files", StaticFiles(...))
存在;确认文件已落盘到/app/uploads
;相对路径url
需要拼域名/IP。 - "Attribute 'app' not found in module 'main'" :
main.py
内必须有app = FastAPI()
;Dockerfile
的启动命令是main:app
。 - 上传大文件 413 :此方案不经 Nginx,默认无严格限制;如接入 Nginx,需调大
client_max_body_size
。 - 权限问题 :确保宿主
/data/fastapi-uploads
对容器用户可写(本文用 1000:1000 和:Z
)。
10. 后续增强(可选)
- Nginx/HTTPS:加一层反向代理提供 80/443、证书、限流、缓存等(对应"方案 B")。
- 监控与告警 :Prometheus + cAdvisor 或简单基于
docker logs
/HEALTHCHECK
。 - 自动化更新:Watchtower 定时拉取新镜像并重启。
- 备份 :定期备份
/data/fastapi-uploads
到对象存储(MinIO/S3/OSS)或异地。