python-3.xdockerflaskdocker-composemariadb

Can't connect Flask app to Mariadb in Docker container


I have a simple flask app in a Docker container that connects to a Mariadb container. I'm using Docker Compose to set up these containers and for some reason there is a problem connecting them. I can get a connection just fine if I run the app and the database normally (on my system without Docker) but it won't work in the container environment for some reason. This app is just a simple test to help me get familiar with Docker.

Here is my flask app code - init.py

from flask import Flask
import os

app = Flask(__name__)

app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') or "a random string"
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:rootpass@database/Toons'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

from App import routes

Here is my flask app code - routes.py

from flask import Flask, request, flash, url_for, redirect, render_template
from App import app
from App.DB_ORM import db, Characters#, Media

print('in routes')
@app.route('/')
@app.route('/index.html')
def index():
    print('in index')

    return render_template('index.html', title = 'Home')


@app.route('/about.html')
def about():
    print('in about')

    return render_template('about.html', title = 'About')


@app.route('/info.html', methods=['GET'])
def info():
    print('in info')
    recs = db.session.execute(db.select(Characters)).scalars().all()
    print(recs)
    return render_template('info.html', title = 'info', records = recs)


@app.route('/media.html', methods=['GET'])
def media():
    print('in media')
    return render_template('media.html', title = 'media')


if __name__ == '__main__':
    app.run()

Here is my flask app code - DB_ORM.py

from flask import Flask
from App import app
from flask_sqlalchemy import SQLAlchemy

# create the database object
db = SQLAlchemy(app)
print(app.config['SQLALCHEMY_DATABASE_URI'])

#one-to-many: one set of Details has many symptoms, treatments, tests, and links
class Characters(db.Model):
    __tablename__ = 'characters'
    id = db.Column(db.Integer, primary_key = True)
    char_name = db.Column(db.String(150))
    char_role = db.Column(db.String(150))


with app.app_context():
    db.create_all() #if these tables don't exist yet create them

and here is my docker file

FROM python:3.13.1-bookworm

WORKDIR /testing_docker

COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt

COPY . .

#RUN apt update && apt -y install mariadb-client

CMD [ "python3", "-m" , "flask", "--app=FlaskBasic.py", "run", "--host=0.0.0.0"]

and here is my compose file

services:
  app:
    image: docker_test_app
    depends_on:
      db:
        condition: service_healthy
        restart: true
    networks:
      flask-net-test:
        aliases:
          - application
    links:
      - db
    ports:
      - "5000:5000"

  db:
    image: mariadb
    healthcheck:
      test: ["CMD", "su", "-", "mysql", "-c", "/usr/bin/mariadb-admin -h localhost -u root -prootpass ping --silent"]
      retries: 5
      timeout: 6s
    networks:
      flask-net-test:
        aliases:
          - database
    ports:
      - "3306"
    environment:
      MARIADB_ROOT_HOST: database
      MARIADB_ROOT_PASSWORD: rootpass
      MARIADB_DATABASE: Toons  #This variable allows you to specify the name of a database to be created on image startup.
      MARIADB_PASSWORD: rootpass
    volumes:
      - /opt/mariadb/backup:/var/lib/mysql
      - ./db/setup.sql:/docker-entrypoint-initdb.d/setup.sql

networks:
  flask-net-test:
    name: flask-net-test

and finally here is the error I am getting

Attaching to app-1, db-1
db-1   | 2025-03-03 19:55:56+00:00 [Note] [Entrypoint]: Entrypoint script for MariaDB Server 1:11.6.2+maria~ubu2404 started.
db-1   | 2025-03-03 19:55:57+00:00 [Warn] [Entrypoint]: /sys/fs/cgroup///memory.pressure not writable, functionality unavailable to MariaDB
db-1   | 2025-03-03 19:55:57+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
db-1   | 2025-03-03 19:55:57+00:00 [Note] [Entrypoint]: Entrypoint script for MariaDB Server 1:11.6.2+maria~ubu2404 started.
db-1   | 2025-03-03 19:55:57+00:00 [Note] [Entrypoint]: MariaDB upgrade not required
db-1   | 2025-03-03 19:55:57 0 [Note] Starting MariaDB 11.6.2-MariaDB-ubu2404 source revision d8dad8c3b54cd09fefce7bc3b9749f427eed9709 server_uid 2KQvNCDvu6PQATgLu/GVimw4hjM= as process 1
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: Compressed tables use zlib 1.3
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: Number of transaction pools: 1
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: Using crc32 + pclmulqdq instructions
db-1   | 2025-03-03 19:55:57 0 [Note] mariadbd: O_TMPFILE is not supported on /tmp (disabling future attempts)
db-1   | 2025-03-03 19:55:57 0 [Warning] mariadbd: io_uring_queue_init() failed with errno 95
db-1   | 2025-03-03 19:55:57 0 [Warning] InnoDB: liburing disabled: falling back to innodb_use_native_aio=OFF
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: Initializing buffer pool, total size = 128.000MiB, chunk size = 2.000MiB
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: Completed initialization of buffer pool
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: File system buffers for log disabled (block size=512 bytes)
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: End of log at LSN=52688
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: Opened 3 undo tablespaces
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: 128 rollback segments in 3 undo tablespaces are active.
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: Setting file './ibtmp1' size to 12.000MiB. Physically writing the file full; Please wait ...
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: File './ibtmp1' size is now 12.000MiB.
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: log sequence number 52688; transaction id 29
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: Loading buffer pool(s) from /var/lib/mysql/ib_buffer_pool
db-1   | 2025-03-03 19:55:57 0 [Note] Plugin 'FEEDBACK' is disabled.
db-1   | 2025-03-03 19:55:57 0 [Note] Plugin 'wsrep-provider' is disabled.
db-1   | 2025-03-03 19:55:57 0 [Note] InnoDB: Buffer pool(s) load completed at 250303 19:55:57
db-1   | 2025-03-03 19:55:59 0 [Note] Server socket created on IP: '0.0.0.0'.
db-1   | 2025-03-03 19:55:59 0 [Note] Server socket created on IP: '::'.
db-1   | 2025-03-03 19:55:59 0 [Warning] 'user' entry 'root@database' ignored in --skip-name-resolve mode.
db-1   | 2025-03-03 19:55:59 0 [Warning] 'proxies_priv' entry '@% root@database' ignored in --skip-name-resolve mode.
db-1   | 2025-03-03 19:55:59 0 [Note] mariadbd: Event Scheduler: Loaded 0 events
db-1   | 2025-03-03 19:55:59 0 [Note] mariadbd: ready for connections.
db-1   | Version: '11.6.2-MariaDB-ubu2404'  socket: '/run/mysqld/mysqld.sock'  port: 3306  mariadb.org binary distribution
db-1   | 2025-03-03 19:56:27 4 [Warning] Aborted connection 4 to db: 'unconnected' user: 'unauthenticated' host: '172.21.0.3' (This connection closed normally without authentication)
app-1  | Traceback (most recent call last):
app-1  |   File "<frozen runpy>", line 198, in _run_module_as_main
app-1  |   File "<frozen runpy>", line 88, in _run_code
app-1  |   File "/usr/local/lib/python3.13/site-packages/flask/__main__.py", line 3, in <module>
app-1  |     main()
app-1  |     ~~~~^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/flask/cli.py", line 1129, in main
app-1  |     cli.main()
app-1  |     ~~~~~~~~^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/click/core.py", line 1082, in main
app-1  |     rv = self.invoke(ctx)
app-1  |   File "/usr/local/lib/python3.13/site-packages/click/core.py", line 1697, in invoke
app-1  |     return _process_result(sub_ctx.command.invoke(sub_ctx))
app-1  |                            ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/click/core.py", line 1443, in invoke
app-1  |     return ctx.invoke(self.callback, **ctx.params)
app-1  |            ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/click/core.py", line 788, in invoke
app-1  |     return __callback(*args, **kwargs)
app-1  |   File "/usr/local/lib/python3.13/site-packages/click/decorators.py", line 92, in new_func
app-1  |     return ctx.invoke(f, obj, *args, **kwargs)
app-1  |            ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/click/core.py", line 788, in invoke
app-1  |     return __callback(*args, **kwargs)
app-1  |   File "/usr/local/lib/python3.13/site-packages/flask/cli.py", line 977, in run_command
app-1  |     raise e from None
app-1  |   File "/usr/local/lib/python3.13/site-packages/flask/cli.py", line 961, in run_command
app-1  |     app: WSGIApplication = info.load_app()  # pyright: ignore
app-1  |                            ~~~~~~~~~~~~~^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/flask/cli.py", line 349, in load_app
app-1  |     app = locate_app(import_name, name)
app-1  |   File "/usr/local/lib/python3.13/site-packages/flask/cli.py", line 245, in locate_app
app-1  |     __import__(module_name)
app-1  |     ~~~~~~~~~~^^^^^^^^^^^^^
app-1  |   File "/testing_docker/FlaskBasic.py", line 2, in <module>
app-1  |     from App import app
app-1  |   File "/testing_docker/App/__init__.py", line 11, in <module>
app-1  |     from App import routes
app-1  |   File "/testing_docker/App/routes.py", line 4, in <module>
app-1  |     from App.DB_ORM import db, Characters#, Media
app-1  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app-1  |   File "/testing_docker/App/DB_ORM.py", line 26, in <module>
app-1  |     db.create_all() #if these tables don't exist yet create them
app-1  |     ~~~~~~~~~~~~~^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/flask_sqlalchemy/extension.py", line 900, in create_all
app-1  |     self._call_for_binds(bind_key, "create_all")
app-1  |     ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/flask_sqlalchemy/extension.py", line 881, in _call_for_binds
app-1  |     getattr(metadata, op_name)(bind=engine)
app-1  |     ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/sql/schema.py", line 5900, in create_all
app-1  |     bind._run_ddl_visitor(
app-1  |     ~~~~~~~~~~~~~~~~~~~~~^
app-1  |         ddl.SchemaGenerator, self, checkfirst=checkfirst, tables=tables
app-1  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app-1  |     )
app-1  |     ^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 3248, in _run_ddl_visitor
app-1  |     with self.begin() as conn:
app-1  |          ~~~~~~~~~~^^
app-1  |   File "/usr/local/lib/python3.13/contextlib.py", line 141, in __enter__
app-1  |     return next(self.gen)
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 3238, in begin
app-1  |     with self.connect() as conn:
app-1  |          ~~~~~~~~~~~~^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 3274, in connect
app-1  |     return self._connection_cls(self)
app-1  |            ~~~~~~~~~~~~~~~~~~~~^^^^^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 148, in __init__
app-1  |     Connection._handle_dbapi_exception_noconnection(
app-1  |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
app-1  |         err, dialect, engine
app-1  |         ^^^^^^^^^^^^^^^^^^^^
app-1  |     )
app-1  |     ^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 2439, in _handle_dbapi_exception_noconnection
app-1  |     raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 146, in __init__
app-1  |     self._dbapi_connection = engine.raw_connection()
app-1  |                              ~~~~~~~~~~~~~~~~~~~~~^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 3298, in raw_connection
app-1  |     return self.pool.connect()
app-1  |            ~~~~~~~~~~~~~~~~~^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/pool/base.py", line 449, in connect
app-1  |     return _ConnectionFairy._checkout(self)
app-1  |            ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/pool/base.py", line 1263, in _checkout
app-1  |     fairy = _ConnectionRecord.checkout(pool)
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/pool/base.py", line 712, in checkout
app-1  |     rec = pool._do_get()
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/pool/impl.py", line 179, in _do_get
app-1  |     with util.safe_reraise():
app-1  |          ~~~~~~~~~~~~~~~~~^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/util/langhelpers.py", line 146, in __exit__
app-1  |     raise exc_value.with_traceback(exc_tb)
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/pool/impl.py", line 177, in _do_get
app-1  |     return self._create_connection()
app-1  |            ~~~~~~~~~~~~~~~~~~~~~~~^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/pool/base.py", line 390, in _create_connection
app-1  |     return _ConnectionRecord(self)
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/pool/base.py", line 674, in __init__
app-1  |     self.__connect()
app-1  |     ~~~~~~~~~~~~~~^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/pool/base.py", line 900, in __connect
app-1  |     with util.safe_reraise():
app-1  |          ~~~~~~~~~~~~~~~~~^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/util/langhelpers.py", line 146, in __exit__
app-1  |     raise exc_value.with_traceback(exc_tb)
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/pool/base.py", line 896, in __connect
app-1  |     self.dbapi_connection = connection = pool._invoke_creator(self)
app-1  |                                          ~~~~~~~~~~~~~~~~~~~~^^^^^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/engine/create.py", line 646, in connect
app-1  |     return dialect.connect(*cargs, **cparams)
app-1  |            ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/sqlalchemy/engine/default.py", line 622, in connect
app-1  |     return self.loaded_dbapi.connect(*cargs, **cparams)
app-1  |            ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
app-1  |   File "/usr/local/lib/python3.13/site-packages/MySQLdb/__init__.py", line 121, in Connect
app-1  |     return Connection(*args, **kwargs)
app-1  |   File "/usr/local/lib/python3.13/site-packages/MySQLdb/connections.py", line 200, in __init__
app-1  |     super().__init__(*args, **kwargs2)
app-1  |     ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
app-1  | sqlalchemy.exc.OperationalError: (MySQLdb.OperationalError) (1130, "Host '172.21.0.3' is not allowed to connect to this MariaDB server")
app-1  | (Background on this error at: https://sqlalche.me/e/20/e3q8)
app-1  | mysql://root:rootpass@database/Toons

the important part of this log I think is

db-1   | 2025-03-03 19:56:27 4 [Warning] Aborted connection 4 to db: 'unconnected' user: 'unauthenticated' host: '172.21.0.3' (This connection closed normally without authentication)

What can I try next?


Solution

  • You're trying to connect to MariaDB as the root user. You also set the MARIADB_ROOT_HOST in your configuration. Because this variable is set, the database only allows connections as the root user from the host named database; that is, itself and not other containers.

    The simplest fix is to remove this line, delete the underlying database storage (sudo rm -rf /opt/mariadb/backup on the host system) so that the environment variables can be considered again, and rerun your application.

    As commenters note, you often don't want to use the root user for your application. Setting a non-root MARIADB_USER and MARIADB_PASSWORD might be a better practice. You'd need to change your application credentials to match.

    While we're updating the configuration, the networking setup is far more complicated than necessary. You never need the obsolete links: option in present-day Docker. Compose provides a network named default and attaches containers to it, and this works just fine for most Compose-scale applications; you do not need a manually-declared network or complex options like network aliases usually.

    This brings the Compose setup to:

    services:
      app:
        image: docker_test_app
        depends_on:
          db:
            condition: service_healthy
            restart: true
        ports:
          - "5000:5000"
        environment:
          # NOTE: your code needs to look at os.environ('SQLALCHEMY_DATABASE_URI') to use this
          SQLALCHEMY_DATABASE_URI: mysql://user:passw0rd@db/Toons'
    
      db:
        image: mariadb
        healthcheck:
          test: ["CMD", "su", "-", "mysql", "-c", "/usr/bin/mariadb-admin -h localhost -u root -prootpass ping --silent"]
          retries: 5
          timeout: 6s
        environment:
          MARIADB_ROOT_PASSWORD: rootpass
          MARIADB_DATABASE: Toons
          MARIADB_USER: user
          MARIADB_PASSWORD: passw0rd
        volumes:
          - /opt/mariadb/backup:/var/lib/mysql
          - ./db/setup.sql:/docker-entrypoint-initdb.d/setup.sql