Heroku部署Django 2.0时遇OSError: [Errno 30]只读文件系统问题
首先,这个问题的核心原因是Heroku的文件系统特性:Heroku的dyno文件系统是临时(ephemeral)且大部分目录只读的,只有/tmp目录是允许写入的,而你当前配置的MEDIA_ROOT指向的是BASE_DIR外的static_cdn/media_root,这个路径不在Heroku允许的可写范围内,所以触发了OSError: [Errno 30] Read-only file system错误。
下面分两种场景给出解决方案:
场景1:临时测试(不要求文件持久化)
如果只是测试功能,不需要生成的音频文件长期保存,可以直接把MEDIA_ROOT指向Heroku的临时目录/tmp:
修改你的App/settings/production.py:
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) MEDIA_URL = '/media/' # 替换为Heroku允许写入的临时目录 MEDIA_ROOT = os.path.join('/tmp', 'static_cdn', 'media_root')
同时,在生成音频文件的代码里,确保目标目录存在(避免路径不存在的错误):
import os import time from django.conf import settings from gtts import gTTS tts_file_name = f"{int(time.time())}.mp3" # 确保tts子目录存在,exist_ok=True避免重复创建报错 tts_dir = os.path.join(settings.MEDIA_ROOT, 'tts') os.makedirs(tts_dir, exist_ok=True) joined_path = os.path.join(tts_dir, tts_file_name) # 生成并保存语音文件 tts = gTTS(text="你的文本内容", lang='zh-cn') tts.save(joined_path)
⚠️ 注意:/tmp目录的文件会在dyno重启(比如部署新代码、dyno休眠唤醒)时被清空,所以不适合生产环境长期存储文件。
场景2:生产环境(需要文件持久化)
生产环境下,必须使用云存储服务来保存媒体文件,Heroku官方推荐使用AWS S3、Cloudinary等服务。这里以AWS S3为例:
步骤1:安装依赖
在你的requirements.txt中添加:
django-storages>=1.13.2 boto3>=1.28.0
然后重新部署到Heroku,或本地执行pip install -r requirements.txt。
步骤2:配置S3存储
修改App/settings/production.py,添加S3相关配置:
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) MEDIA_URL = '/media/' # 引入环境变量(敏感信息不要硬编码) import os AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID') AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY') AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME') AWS_S3_REGION_NAME = 'us-east-1' # 替换为你的S3存储桶所在区域 AWS_S3_CUSTOM_DOMAIN = f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com" # 设置媒体文件存储到S3 DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' MEDIA_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/media/" # 可选:设置缓存控制,提升访问速度 AWS_S3_OBJECT_PARAMETERS = { 'CacheControl': 'max-age=86400', }
步骤3:在Heroku设置环境变量
通过Heroku CLI设置敏感信息(避免硬编码到代码中):
heroku config:set AWS_ACCESS_KEY_ID="你的AWS访问密钥ID" heroku config:set AWS_SECRET_ACCESS_KEY="你的AWS秘密访问密钥" heroku config:set AWS_STORAGE_BUCKET_NAME="你的S3存储桶名称" heroku config:set AWS_S3_REGION_NAME="你的存储桶区域"
步骤4:调整文件保存代码
不需要修改保存路径的代码,因为django-storages会自动将settings.MEDIA_ROOT的写入请求转发到S3,原有的os.path.join(settings.MEDIA_ROOT, 'tts', tts_file_name)会自动映射到S3的对应路径。
这样修改后,生成的音频文件会直接保存到S3,既解决了Heroku文件系统只读的问题,又能持久化存储媒体文件。
内容的提问来源于stack exchange,提问作者Anuj TBE

