Docker容器中Django媒体文件未存入MEDIA_ROOT问题求助
问题背景
Docker化部署的Django+React+PostgreSQL+Nginx项目,开发环境(./manage.py runserver)中生成PNG图片并保存到ImageField、作为邮件附件发送完全正常,但Docker Compose部署后:
- 图片实际保存到容器内的
BASE_DIR/email_images/...路径,而非settings.MEDIA_ROOT(BASE_DIR/media/email_images/...) - 运行时出现错误:
[Errno 2] No such file or directory: '/media/email_images/2023/04/26/gears_table_bQmiNa6.png' - 静态文件功能正常,仅媒体文件路径异常
相关核心配置:
settings.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STATIC_URL = '/django_static/' STATIC_ROOT = os.path.join(BASE_DIR, 'django_static') MEDIA_URL = 'media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
models.py(SummaryEmail模型)
class SummaryEmail(models.Model): # ... 其他字段省略 gears_image = models.ImageField(upload_to="email_images/%Y/%m/%d", blank=True) steps_image = models.ImageField(upload_to="email_images/%Y/%m/%d", blank=True)
解决方案
1. 修正图片保存逻辑:使用Django文件存储API
问题大概率出在手动硬编码文件路径,而非通过Django的ImageField内置方法保存。确保生成图片后,通过模型实例的ImageField保存,而非直接写入磁盘:
from django.core.files.base import ContentFile from io import BytesIO from PIL import Image # 示例:生成图片并保存到ImageField def generate_and_save_image(summary_email_instance): # 1. 生成图片逻辑(示例用PIL创建测试图) img = Image.new('RGB', (100, 100), color='red') buffer = BytesIO() img.save(buffer, format='PNG') buffer.seek(0) # 2. 通过ImageField保存,Django自动处理MEDIA_ROOT路径 summary_email_instance.gears_image.save( 'gears_table.png', ContentFile(buffer.read()), save=True # 保存模型实例 )
如果之前是用open("email_images/...", "wb")这类代码直接写文件,会保存到当前工作目录(BASE_DIR),而非MEDIA_ROOT。
2. 确保容器内MEDIA_ROOT目录存在且权限正确
在Docker入口脚本(如wsgi-entrypoint.sh)中添加目录创建和权限配置,避免容器启动后MEDIA_ROOT不存在:
#!/bin/sh # 创建MEDIA_ROOT目录并设置权限 mkdir -p $DJANGO_MEDIA_ROOT chown -R www-data:www-data $DJANGO_MEDIA_ROOT # 执行其他启动命令(如migrate、gunicorn) python manage.py migrate gunicorn nabuconnect.wsgi:application --bind 0.0.0.0:8000
同时在Dockerfile中定义环境变量:
ENV DJANGO_MEDIA_ROOT=/app/media # 对应settings.py中的MEDIA_ROOT路径
3. 修正MEDIA_URL配置
将MEDIA_URL改为带开头斜杠的格式,避免路径拼接异常:
MEDIA_URL = '/media/' # 原配置为'media/',缺少开头斜杠
4. 配置Docker卷挂载
在docker-compose.dev.yml中确保本地media目录挂载到容器内的MEDIA_ROOT路径:
services: backend: # ... 其他配置省略 volumes: - ./backend:/app # 代码挂载 - ./backend/media:/app/media # 媒体文件挂载,对应容器内MEDIA_ROOT
5. 生产环境Nginx代理媒体文件
由于生产环境DEBUG=False时,Django的static()函数不再生效,需通过Nginx代理media请求。在Nginx的default.conf中添加:
location /media/ { alias /app/media/; # 容器内MEDIA_ROOT的绝对路径 expires 30d; add_header Cache-Control "public, max-age=2592000"; }
6. 邮件附件使用ImageField的本地路径
发送邮件时,使用ImageField的path属性(本地绝对路径)而非url属性(Web访问URL):
from django.core.mail import EmailMessage def send_summary_email(summary_email_instance): email = EmailMessage( '手术汇总报告', '附件为手术汇总图片', 'from@example.com', ['to@example.com'] ) # 使用path属性获取本地文件路径 email.attach_file(summary_email_instance.gears_image.path) email.send()
错误提示中的/media/...路径正是因为误用了url属性(如summary_email_instance.gears_image.url),导致代码尝试访问Web路径而非本地文件路径。
验证步骤
进入容器执行命令,确认MEDIA_ROOT路径:
docker exec -it nabu_prod-backend-1 python manage.py shell在shell中执行:
from django.conf import settings print(settings.BASE_DIR) print(settings.MEDIA_ROOT)确保输出的MEDIA_ROOT是预期的路径,且目录存在。
检查容器内media目录权限:
docker exec -it nabu_prod-backend-1 ls -l /app/media确保所有者是www-data或Django进程运行的用户。
内容的提问来源于stack exchange,提问作者natrix

