I’m hosting Django (backend) on Fly and React (frontend) on Cloudflare.
Here’s my frontend code to upload an image:
export const createPostApi = async (imageFile, text) => {
const formData = new FormData();
formData.append("text", text);
if (imageFile) formData.append("image", imageFile);
const response = await api.post("/create-post/", formData, {
headers: { "Content-Type": "multipart/form-data" },
});
return response.data;
};
which goes to this view:
@api_view(['POST'])
def CreatePost(request):
data = request.data.copy()
data['text'] = normalize_whitespace(data.get('text', '')).strip()
serializer = PostSerializer(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
serializer.save(user=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
and here’s the serializer:
class PostSerializer(serializers.ModelSerializer):
is_mine = serializers.SerializerMethodField()
username = serializers.CharField(source='user.username', read_only=True)
name = serializers.CharField(source='user.first_name', read_only=True)
profile_picture = serializers.ImageField(source='user.profile_picture', read_only=True)
like_count = serializers.SerializerMethodField()
is_liked = serializers.SerializerMethodField()
comment_count = serializers.SerializerMethodField()
formatted_date = serializers.SerializerMethodField()
is_edited = serializers.SerializerMethodField()
def get_is_mine(self, obj):
request = self.context.get('request', None)
if not request or not request.user.is_authenticated:
return False
return obj.user == request.user
def get_like_count(self, obj):
return obj.likes.count()
def get_is_liked(self, obj):
request = self.context.get('request')
return request.user in obj.likes.all() if request and request.user.is_authenticated else False
def get_comment_count(self, obj):
return obj.comments.count()
def get_formatted_date(self, obj):
return obj.created_at.strftime("%d/%m/%Y %H:%M")
def get_is_edited(self, obj):
return obj.edited
class Meta:
...
and the Post model:
class Post(models.Model):
user = models.ForeignKey(MyUser, on_delete=models.CASCADE, related_name='posts')
image = models.ImageField(upload_to=post_upload_path, blank=True, null=True)
text = models.TextField(max_length=1000, blank=False)
created_at = models.DateTimeField(auto_now_add=True)
likes = models.ManyToManyField(MyUser, related_name='liked_posts', blank=True)
edited = models.BooleanField(default=False)
def __str__(self):
return f"{self.user.username}'s post"
and the post_upload_path:
def post_upload_path(instance, filename):
ext = os.path.splitext(filename)[1]
ts = timezone.now().strftime("%Y%m%d%H%M%S")
uid = uuid.uuid4().hex
return f"posts/post_{instance.user.id}_{ts}_{uid}{ext}"
and settings.py:
MEDIA_URL = '/api/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Anyway, I hope this is enough background. This works in local (classic). But when I mount and try to upload it on a Fly volume, it refuses to work. Here’s what I’ve tried:
> fly status
App
Name = appname
Owner = personal
Hostname = appname.fly.dev
Image = appname:deployment-0123456789
Machines
PROCESS ID VERSION REGION STATE ROLE CHECKS LAST UPDATED
app e1234567891000 17 yyz started 2025-06-05T10:32:33Z
> fly volumes list
ID STATE NAME SIZE REGION ZONE ENCRYPTED ATTACHED VM CREATED AT
vol_somerandomid1010 created lipupona_vol 1GB yyz 75ec true e1234567891000 3 hours ago
So it seems the volume is attached to my VM. Here’s my relevant fly.toml:
[[statics]]
guest_path = "/code/media"
url_prefix = "/api/media/"
I tried this:
root@e1234567891000:/code# ls /code/media/posts/
post_1_20250604160752_9bfd744909a2478b90fd067c115be9fc.jpg
This is a post that’s on my local machine. So it shows the posts on my local machine but does not let me upload. I also tried:
root@e1234567891000:/code# echo "Hello" > /code/media/test.txt
and this works when I go to appname.fly.dev/api/media/test.txt.
That’s pretty much all. I don’t know where the upload goes and why it doesn’t work. Any kind of help is appreciated. Thank you.