Docker Compose - The ImageField field in Django cannot upload images when I migrate the database from sqlite3 to MySQL in Docker Compose

I have the Article model in Django blog app

File /backend/blog/models.py

class Article(models.Model):
    class Status(models.TextChoices):
        DRAFT = 'DF', 'Draft'
        PUBLISHED = 'PB', 'Published'

    title = models.CharField(max_length=255)
    slug = models.SlugField(max_length=100, blank=True, unique=True)
    content = MDTextField(null=True, blank=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    comments = models.IntegerField(default=0)
    reactions = models.IntegerField(default=0)
    updated = models.DateTimeField(auto_now=True)
    category = models.ForeignKey(Category, related_name='articles', on_delete=models.SET_NULL, null=True)
    tags = models.ManyToManyField(Tag)
    image_background = models.ImageField(upload_to='articles_images/', blank=True, null=True)
    image_url = models.URLField(max_length=500, blank=True, null=True)  # Updated field to store image URL from Firebase
    intro = models.TextField()
    estimate_time = models.CharField(max_length=50)
    type = models.ForeignKey(Type, on_delete=models.SET_NULL, null=True)
    publish = models.DateTimeField(default=timezone.now)
    status = models.CharField(max_length=2,
                              choices=Status.choices,
                              default=Status.DRAFT)

Next, I porting the default database SQLITE3 to MYSQL for Django project

File /backend/settings.py

# ! Config database for mysql
DATABASES = {
    "default": {
        "ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.sqlite3"),
        "NAME": os.environ.get("SQL_DATABASE", os.path.join(BASE_DIR, 'db.sqlite3')),
        "USER": os.environ.get("SQL_USER", "myuser"),
        "PASSWORD": os.environ.get("SQL_PASSWORD", "myuserpassword"),
        "HOST": os.environ.get("SQL_HOST", "localhost"),
        "PORT": os.environ.get("SQL_PORT", "3306"),
        'OPTIONS': {
            'charset': 'utf8mb4',
        }
    }
}

I also config the path media to upload image /backend/settings.py

STATIC_URL = 'static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
STATICFILES_DIRS = (os.path.join(BASE_DIR,"static"), )

I used the docker-compose.yml to build image and run container for backend (django), frontend (nextjs) and database (mysql) to develop my project

version: '3.8'

services:
  database:
    image: mysql:9.1.0
    container_name: mysql-container
    volumes:
      # mysql_data: data stogare managed by Docker: /var/lib/docker/volumes/
      # See info: docker volume inspect mysql_data
      # /var/lib/mysql: is the default path in container MySQL
      - mysql_data:/var/lib/mysql
    restart: always
    env_file:
      - .env
    environment:
      - MYSQL_ROOT_PASSWORD=${SQL_ROOT_PASSWORD}
      - MYSQL_USER=${SQL_USER}
      - MYSQL_PASSWORD=${SQL_PASSWORD}
      - MYSQL_DATABASE=${SQL_DATABASE}  
      - MYSQL_TCP_PORT=${MYSQL_TCP_PORT}
    ports:
      - "3306:3306"
    networks:
      - default

  backend:
    build:
      dockerfile: Dockerfile
      context: ./backend          # Find location the Dockerfile of backend in the `backend` directory.
    image: django
    container_name: django-container
    restart: always
    env_file:
      - .env
    volumes:
      # - <path_in_host>:<path_in_container>
      - ./backend:/app  # Mount all contents of folder backend into container
      - ./backend/media:/app/media
    ports:
      - "3001:3001"
    command: python manage.py runserver 0.0.0.0:3001
    depends_on:
      - database
    networks:
      - default

  frontend:
    build:
      dockerfile: Dockerfile
      context: ./frontend
    image: nextjs
    container_name: nextjs-container
    restart: always
    volumes:
      - ./frontend:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    command: sh -c "export PATH=$$PATH:/app/node_modules/.bin && npm run dev"
    depends_on:
      - backend
    networks:
      - default

networks:
  default:
    driver: bridge

volumes:
  mysql_data:

However, I CANNOT upload any images from

image_background = models.ImageField(upload_to='articles_images/', blank=True, null=True)

even though everything WORKED FINE with SQLITE3 before. I have checked the access permissions of the /media directory in the container, and all directories have read, write, and execute permissions.

Please help me fix this issue. Thank you so much

-------------------------------------------

These are my entire the /backend/blog/models.py

from django.db import models
from django.utils import timezone
import shutil, os, re
from django.contrib.auth.models import User
from .firebase import upload_image_to_firebase
from ckeditor.fields import RichTextField
from ckeditor_uploader.fields import RichTextUploadingField 
import os
from mdeditor.fields import MDTextField
from django.conf import settings
from slugify import slugify
from .logger import Logger
from .github_discussions_api import get_comment_react

LOGGER = Logger("Create model Article")

class Type(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Category(models.Model):
    name = models.CharField(max_length=100)
    hex_color = models.CharField(max_length=7, default='#FFFFFF')
    emoji = models.CharField(max_length=10, blank=True, null=True)

    def __str__(self):
        return self.name


class Tag(models.Model):
    name = models.CharField(max_length=100)
    hex_color = models.CharField(max_length=7, default='#FFFFFF')

    def __str__(self):
        return self.name

def find_markdown_images(text):
    pattern = r'!\[(.*?)\]\(((?:.*?\s*?)+?)\s*(?:\"(.*?)\")?\)'
    matches = re.findall(pattern, text)
    filtered_matches = [(match[0], match[1]) for match in matches if not match[1].startswith(('http:', 'https:'))]
    return filtered_matches
def copy_image_background_to_article_folder(app_path, slug_article, image_name):
    #image_name = articles_images/describle_project.png
    image_name = image_name.split('/')[1]
    new_path = f"/app/media/articles_images/{slug_article}/{image_name}"
    os.makedirs(os.path.dirname(new_path), exist_ok=True)
    shutil.copy(app_path, new_path)
    return new_path

class Article(models.Model):
    class Status(models.TextChoices):
        DRAFT = 'DF', 'Draft'
        PUBLISHED = 'PB', 'Published'

    title = models.CharField(max_length=255)
    slug = models.SlugField(max_length=100, blank=True, unique=True)
    content = MDTextField(null=True, blank=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    comments = models.IntegerField(default=0)
    reactions = models.IntegerField(default=0)
    updated = models.DateTimeField(auto_now=True)
    category = models.ForeignKey(Category, related_name='articles', on_delete=models.SET_NULL, null=True)
    tags = models.ManyToManyField(Tag)
    image_background = models.ImageField(upload_to='articles_images/', blank=True, null=True)
    image_url = models.URLField(max_length=500, blank=True, null=True)  # Updated field to store image URL from Firebase
    intro = models.TextField()
    estimate_time = models.CharField(max_length=50)
    type = models.ForeignKey(Type, on_delete=models.SET_NULL, null=True)
    publish = models.DateTimeField(default=timezone.now)
    status = models.CharField(max_length=2,
                              choices=Status.choices,
                              default=Status.DRAFT)

    class Meta:
        ordering = ['-publish']
        indexes = [
            models.Index(fields=['-publish']),
        ]

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title, max_length=100)

        update_fields = []

        if self.pk is None:
            super().save(*args, **kwargs)
        else:
            update_fields.append('slug')

        if self.image_background:
            full_path = f"/app/media/{self.image_background.name}"
            full_path_article = copy_image_background_to_article_folder(full_path, self.slug, self.image_background.name)
            self.image_url = upload_image_to_firebase(self.title, full_path_article, os.path.basename(full_path_article))
            update_fields.append('image_url')

        image_tuples = find_markdown_images(self.content)
        for alt_text, image_path in image_tuples:
            full_path = os.path.join(settings.BASE_DIR, image_path.strip('/'))
            if os.path.exists(full_path):
                url_image = upload_image_to_firebase(self.title, full_path, os.path.basename(image_path))
                pattern = r'\!\[' + re.escape(alt_text) + r'\]\(' + re.escape(image_path) + r'(\s+".*?")?\)'
                self.content = re.sub(pattern, f'![{alt_text}]({url_image})', self.content)
            else:
                LOGGER.error(f"File not found: {full_path} when upload image into content")
        update_fields.append('content')

        comments, reactions = get_comment_react(self.slug)
        self.comments = comments
        self.reactions = reactions
        update_fields.extend(['comments', 'reactions'])

        super().save(*args, **kwargs, update_fields=update_fields if update_fields else None)

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    name = models.CharField(max_length=50, blank=True, null=True)
    about_me = MDTextField(null=True, blank=True)
    short_intro = models.CharField(max_length=255, blank=True, null=True)
    occupation = models.CharField(max_length=255, blank=True, null=True)
    github = models.URLField(blank=True, null=True)
    github_display_name = models.CharField(max_length=255, blank=True, null=True)
    facebook = models.URLField(blank=True, null=True)
    facebook_display_name = models.CharField(max_length=255, blank=True, null=True)
    linkedin = models.URLField(blank=True, null=True)
    linkedin_display_name = models.CharField(max_length=255, blank=True, null=True)
    leetcode = models.URLField(blank=True, null=True)
    leetcode_display_name = models.CharField(max_length=255, blank=True, null=True)
    email = models.EmailField(blank=True, null=True)
    email_display_name = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        return self.user.username

class TimeLine(models.Model):
    date = models.CharField(max_length=50)
    title = models.CharField(max_length=255)
    corporation = models.CharField(max_length=255)
    description = models.TextField()
    iconPath = models.CharField(max_length=255)
    toolAndService = models.JSONField()
    type = models.CharField(max_length=50)
    index = models.IntegerField()

    def __str__(self):
        return self.title

I tried debug the permission my folder /app/media

Back to Top