在基于 Docker 的现代 Web 开发中,"用户上传图片并展示" 看似简单,却常常因权限、路径、网络隔离等问题卡住数小时。本文将通过小编自己踩坑案例,带你彻底搞懂:
- ✅ 如何让 FastAPI 正确保存上传文件
- ✅ 如何让 Nginx 容器高效返回静态图片
- ✅ 为什么不能直接在 Nginx 配置里写宿主机路径
- ✅ 如何构建支持多项目的生产级部署架构
一、问题背景
你有一个基于 Docker Compose 的应用:
note-backend:FastAPI,处理/api/uploadnote-frontend:Vue 前端- 你想通过外层
nginx-proxy容器统一入口,并直接返回/uploads/xxx.jpg
但你遇到了两个经典错误:
-
上传时报错 :log
编辑
PermissionError: [Errno 13] Permission denied: '/app/uploads/xxx.jpg' -
上传成功后访问图片返回 :http
编辑
HTTP/1.1 404 Not Found nginx/1.29.4
别急,这背后是 容器化部署中最常见的认知误区。
二、核心原则:职责分离 + 路径解耦
✅ 正确架构图
text
用户
│
▼
[nginx-proxy 容器] ← 监听 80/443(外层网关)
│
├── /uploads/ → 直接返回静态文件(不经过任何后端)
└── / → 代理到 note-frontend 容器
→ API 请求 → note-backend (FastAPI)
🔑 三大设计原则
表格
| 组件 | 职责 | 路径视角 |
|---|---|---|
| FastAPI | 接收上传,写入磁盘 | 容器内路径 (如 /app/uploads) |
| Nginx 容器 | 返回静态文件 | 容器内路径 (如 /var/www/uploads) |
| 宿主机 | 持久化存储 | 绝对路径 (如 /home/ubuntu/app/backend/uploads) |
💡 关键认知 :容器是隔离的!配置中的路径永远是 容器内部视角,不是宿主机!
三、问题 1:Permission denied ------ 权限迷局
❌ 错误现象
python
with open("/app/uploads/xxx.jpg", "wb") as f:
...
# 报错:Permission denied
🔍 根本原因
- 宿主机目录
/home/ubuntu/app/backend/uploads所有者为ubuntu(UID=1000) - FastAPI 容器默认以非 root 用户(如 UID=65534)运行
- 容器进程无权写入该目录
✅ 解决方案(二选一)
方案 A:快速修复(适合个人项目)
sudo chmod -R 777 /home/ubuntu/app/backend/uploads
方案 B:规范做法(推荐生产环境)
在 docker-compose.yml 中指定用户 UID/GID:
services:
backend:
build: ./backend
user: "1000:1000" # 与宿主机 ubuntu 用户一致
volumes:
- ./backend/uploads:/app/uploads
✅ 验证:
docker exec note-backend id应输出uid=1000 gid=1000
四、问题 2:404 Not Found ------ 路径认知错位
❌ 典型错误配置
# nginx.conf(运行在容器内!)
location /uploads/ {
alias /home/ubuntu/app/backend/uploads/; # ❌ 这是宿主机路径!
}
🔍 为什么错?
Nginx 容器无法直接访问宿主机文件系统。它会在 自己容器内的 /home/ubuntu/... 找文件------而这个目录根本不存在!
✅ 正确做法:两步走
第一步:Nginx 配置使用 容器内路径
# /home/ubuntu/myweb/nginx/conf.d/note.conf
server {
listen 80;
server_name zhangyahui.top;
location /uploads/ {
alias /var/www/uploads/; # ✅ 容器内路径,末尾必须有 /
expires 30d;
add_header Cache-Control "public";
}
location / {
proxy_pass http://note-frontend; # 同 Docker 网络,直接用容器名
}
}
⚠️ 注意:
- 必须用
alias,不能用rootalias路径末尾必须加/,否则路径拼接错误
第二步:启动 Nginx 容器时挂载 volume
docker run -d \
--name nginx-proxy \
--network app_app-net \ # 加入自定义网络
-p 80:80 -p 443:443 \
-v /home/ubuntu/app/backend/uploads:/var/www/uploads:ro \ # 👈 关键挂载
-v /home/ubuntu/myweb/nginx/conf.d:/etc/nginx/conf.d:ro \
-v /home/ubuntu/myweb/ssl:/etc/nginx/ssl:ro \
--restart unless-stopped \
nginx:alpine
五、完整配置清单
1. FastAPI 代码(main.py 或路由文件)
from pathlib import Path
UPLOAD_DIR = Path("/app/uploads")
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
@app.post("/upload")
async def upload(file: UploadFile):
filepath = UPLOAD_DIR / file.filename
with open(filepath, "wb") as f:
f.write(await file.read())
return {"url": f"/uploads/{file.filename}"}
2. docker-compose.yml
version: '3.8'
services:
backend:
build: ./backend
container_name: note-backend
user: "1000:1000" # 可选:解决权限问题
volumes:
- ./backend/uploads:/app/uploads
networks:
- app-net
frontend:
build: ./frontend
container_name: note-frontend
networks:
- app-net
networks:
app-net:
name: app_app-net
3. 启动 Nginx 容器命令
# 先清理旧容器
docker stop nginx-proxy && docker rm nginx-proxy
# 启动新容器(带挂载)
docker run -d \
--name nginx-proxy \
--network app_app-net \
-p 80:80 -p 443:443 \
-v /home/ubuntu/app/backend/uploads:/var/www/uploads:ro \
-v /home/ubuntu/myweb/nginx/conf.d:/etc/nginx/conf.d:ro \
nginx:alpine
六、验证与调试
1. 检查文件是否上传成功
ls -l /home/ubuntu/app/backend/uploads/
2. 检查 Nginx 容器是否看到文件
docker exec nginx-proxy ls -l /var/www/uploads/
3. 测试访问
curl -I http://localhost/uploads/test.jpg
# 应返回:HTTP/1.1 200 OK
4. 查看日志
# Nginx 错误日志
docker logs nginx-proxy
# FastAPI 日志
docker logs note-backend
七、多项目扩展建议
若服务器部署多个项目(如 blog、shop、admin),只需:
-
每个项目独立 uploads 目录:text
/home/ubuntu/blog/backend/uploads /home/ubuntu/shop/backend/uploads -
Nginx 配置多个
server块:nginxserver { server_name blog.your.com; location /uploads/ { alias /var/www/blog-uploads/; } } server { server_name shop.your.com; location /uploads/ { alias /var/www/shop-uploads/; } } -
启动 Nginx 容器时挂载多个目录:bash 编辑
-v /home/ubuntu/blog/backend/uploads:/var/www/blog-uploads:ro -v /home/ubuntu/shop/backend/uploads:/var/www/shop-uploads:ro
八、常见误区总结
表格
| 误区 | 正确做法 |
|---|---|
| 在 Nginx 配置中写宿主机路径 | 配置用容器路径,通过 -v 挂载 |
用 root 代替 alias |
location /path/ { alias /real/path/; } |
忽略 alias 末尾的 / |
必须写成 /path/ |
| 让 frontend 容器占 80 端口 | 外层 Nginx 统一入口,支持多项目 |
| 不处理权限直接部署 | 开发用 chmod 777,生产用 user: "1000:1000" |
九、结语
图片上传看似简单,但在容器化、微服务架构下,涉及 权限、路径、网络、配置 四重维度。只有理解:
- 容器是隔离的
- 路径分宿主机/容器两种视角
- 静态资源应由 Nginx 直接返回
才能构建出高性能、可维护、可扩展的系统。
本文所有配置已在 Ubuntu 22.04 + Docker 24 + FastAPI + Nginx 容器环境中验证通过。
你踩过的坑,别人正在踩;你总结的经验,就是价值。