-1

I'm trying to build a Dockerized Django application with a MySQl database, but am unable to connect to the database when running docker-compose up

My Dockerfile is:

# Use the official Python image as the base image
FROM python:3.10

# Set environment variables
ENV PYTHONUNBUFFERED 1

# Set the working directory inside the container
WORKDIR /app

# Copy the requirements file and install dependencies
COPY requirements.txt /app/
RUN pip install -r requirements.txt

CMD ["sh", "-c", "python manage.py migrate"]

# Copy the project files into the container
COPY . /app/

And docker-compose.yml looks like:

version: '3'
services:
  db:
    image: mysql:8
    ports:
      - "3306:3306"
    environment:
      - MYSQL_DATABASE='model_db'
      - MYSQL_USER='root'
      - MYSQL_PASSWORD='password'
      - MYSQL_ROOT_PASSWORD='password'
    volumes:
      - /tmp/app/mysqld:/var/run/mysqld
      - ./db:/var/lib/mysql
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/app
    ports:
      - "8000:8000"
    depends_on:
      - db
    env_file:
      - .env

I'm using a .env file to define environment variables, and it is:

MYSQL_DATABASE=model_db
MYSQL_USER=root
MYSQL_PASSWORD=password
MYSQL_ROOT_PASSWORD=password
MYSQL_HOST=db

These are then loaded to the app settings.py like this:

BASE_DIR = Path(__file__).resolve().parent.parent
env = environ.Env()

if READ_DOT_ENV_FILE := env.bool("DJANGO_READ_DOT_ENV_FILE", default=True):
    # OS environment variables take precedence over variables from .env
    env.read_env(env_file=os.path.join(BASE_DIR, '.env'))

MYSQL_DATABASE = env('MYSQL_DATABASE', default=None)
MYSQL_USER = env('MYSQL_USER', default=None)
MYSQL_PASSWORD = env('MYSQL_PASSWORD', default=None)
MYSQL_ROOT_PASSWORD = env('MYSQL_ROOT_PASSWORD',default=None)
MYSQL_HOST = env('MYSQL_HOST', default=None)

# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'MYSQL_DATABASE': MYSQL_DATABASE,
        'USER': MYSQL_USER,                      # Not used with sqlite3.
        'PASSWORD': MYSQL_PASSWORD,                  # Not used with sqlite3.
        'MYSQL_ROOT_PASSWORD': MYSQL_ROOT_PASSWORD,
        'HOST': MYSQL_HOST,
        'PORT': 3306,
    },
}

When I run docker-compose up it errors out with _mysql_connector.MySQLInterfaceError: Can't connect to MySQL server on 'db:3306' (99)

Any suggestions?

Cole Robertson
  • 599
  • 1
  • 7
  • 18
  • i think because you set the `HOST` to localhost. Just remove it from the mysql container config in the compose file and specify the host in your `.env` with `db` (which is the name you gave the service in your `docker-compose` – Kevin Aug 18 '23 at 20:34
  • "*`MYSQL_HOST=localhost`*" - `localhost` refers to the container itself. Within the `web`-container, there is on process listening on port `3306`. If we want to communicate with the `db`-container, we reference it by the container name (`db`). – Turing85 Aug 18 '23 at 20:35
  • Just replace `MYSQL_HOST=localhost` to `MYSQL_HOST=db` in the `.env` Docker will automatically get Internal IP of db container. – Ahtisham Aug 18 '23 at 20:46
  • Thanks, but now this throws the error: `Can't connect to MySQL server on 'db:3306' (111)` – Cole Robertson Aug 18 '23 at 21:00
  • I don't know that it's helpful, but the db container outputs indicate it's ready for connections: `X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock` `[Server] /usr/sbin/mysqld: ready for connections. Version: '8.1.0' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.` – Cole Robertson Aug 18 '23 at 21:29
  • Can you update the Question with the changes you made to the `docker-compose` file and also to the `.env` file? – Kevin Aug 19 '23 at 05:08
  • That plugin is unrelated to your actual DB being available https://stackoverflow.com/a/56720792/6336357. Did you set a bind address in the mysql config? – Rick Rackow Aug 21 '23 at 07:51
  • @Kevin That's done now. – Cole Robertson Aug 21 '23 at 17:49
  • @ColeRobertson whats the purpose to map the mysqld? – Kevin Aug 22 '23 at 10:20

2 Answers2

2

There some steps i think you should do:

  1. You don't need to expose the mysql port because the containers are in the same network
  2. You don't need CMD in your Dockerfile. Just do this in your command directive in your docker-compose:
command: python3 manage.py migrate && python3 manage.py runserver 0.0.0.0:8000

Other things seems right.

Hope it helps !

Saleh
  • 70
  • 7
0

I've fixed the issues(s), which were several.

  1. The host was wrong. Following several comments, I changed "MYSQL_HOST=localhost" to "MYSQL_HOST=db", i.e. the container name.

  2. The web container was starting before the database server was ready, causing the connection error, because depends_on will only wait for a container to be up, not actually fully initialised. Instituting a health_check, following this and this solved the issue.

  3. python3 manage.py migrate would not work unless I created a DATABASE_URL variable, following this, where DATABASE_URL == mysql://<username>:<password>@<host>:<port>/<db_name> (see here)

The final working Dockerfile, docker-compose.yml, settings.py and .env files were:

Dockerfile

# Use the official Python image as the base image
FROM python:3.10

# Set environment variables
ENV PYTHONUNBUFFERED 1

# Set the working directory inside the container
WORKDIR /app

# Copy the requirements file and install dependencies
COPY requirements.txt /app/
RUN pip install -r requirements.txt

# Copy the project files into the container
COPY . /app/

.env

MYSQL_DATABASE=modeldb
MYSQL_USER=mysql
MYSQL_PASSWORD=mypass
MYSQL_HOST=db
DATABASE_URL=mysql://mysql:mypass@db:3306/modeldb

settings.py

MYSQL_DATABASE = env('MYSQL_DATABASE', default=None)
MYSQL_USER = env('MYSQL_USER', default=None)
MYSQL_PASSWORD = env('MYSQL_PASSWORD', default=None)
MYSQL_HOST = env('MYSQL_HOST', default=None)
DATABASE_URL = os.environ.get('DATABASE_URL', '')

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'MYSQL_DATABASE': MYSQL_DATABASE,
        'USER': MYSQL_USER,                      # Not used with sqlite3.
        'PASSWORD': MYSQL_PASSWORD,                  # Not used with sqlite3.
        'HOST': MYSQL_HOST,
        'PORT': 3306,
    },
}

if DATABASE_URL:
    import dj_database_url
    DATABASES['default'] = dj_database_url.config(default=DATABASE_URL)

docker-compose.yml

version: '3'
services:
  db:
    image: mysql:8
    environment:
      MYSQL_DATABASE: 'modeldb'
      MYSQL_USER: 'mysql'
      MYSQL_PASSWORD: 'test'
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
    volumes:
      - ./db_django:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] # Command to check health.
      interval: 10s # Interval between health checks.
      timeout: 10s # Timeout for each health checking.
      retries: 20 # How many times retries.
      start_period: 30s # Warm up wait period
  web:
    build: .
    command: >
      sh -c "python manage.py collectstatic --noinput &&
             python manage.py migrate &&
             python manage.py runserver 0.0.0.0:8000"
    volumes:
      - .:/app
    ports:
      - "8000:8000"
    depends_on:
      db:
        condition: service_healthy
    env_file:
      - .env
Cole Robertson
  • 599
  • 1
  • 7
  • 18