LinkORB Engineering

Connect a containerized Symfony application to a containerized database in [#docker]

Compose runs multiple containers as components of a single (sandboxed) application. It treats each container as a service and handles operations such as network sharing between containers. These features make Compose a great solution for containerized Symfony applications that require a database. For example, it allows communication between both containers without having the user explicitly create a Docker network and add each container to that network.

As will be discussed further below, Compose leverages the contents of a docker-compose.yml file for its instructions.

Prerequisites

While not a hard requirement, please consider reading How to create and containerize a Symfony application with a shared Docker image. It discusses the Dockerfile, resource permissions, and other concepts referenced in this guide.

Step 1: Define the MariaDB service

The Doctrine ORM package installed when creating a Symfony application creates the docker-compose.override.yml and docker-compose.yml files saved at the root of the project directory. These are Compose files configured to run a PostgreSQL database and a mailer service by default.

Reconfigure these Compose files to connect the Symfony application to a containerized MariaDB database follows:

  1. Replace the contents of the docker-compose.yml file with the following code:

    version: '3'
    
    services:
       db:
          image: mariadb:10.7.8
          restart: always
          ports:
             - "3306:3306"
          environment:
             MARIADB_DATABASE: ${MARIADB_DATABASE:-app}
             MARIADB_USER: ${MARIADB_USER:-app}
             MARIADB_PASSWORD: ${MARIADB_PASSWORD:-!ChangeMe!}
             MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:-!ChangeMe!}
    

    The above instructs Compose to do the following:

    1. Use version 3 of Compose file format
    2. Create a database service named db using mariadb version 10.7.8 Docker image.
    3. Always restart the db service if it dies while running.
    4. Forward port 3306 of the host computer to port 3306 of the db service’s container.
    5. Set the following default values to the following environment variables and overwrite the default values if the user or container sets environment variables to a different values.

    Notice that the environment variables in the above Compose file are hard-coded. To override the default values hard-coded to the environment variables, set the same variables in the .env file at the root of the project directory as shown below:

    MARIADB_DATABASE=<db_name>
    MARIADB_USER=<db_user>
    MARIADB_PASSWORD=<db_password>
    MARIADB_ROOT_PASSWORD=<db_root_password>
    
  2. Delete the docker-compose.override.yml file at the root of the project directory.

Step 2: Define the Symfony application service

Assuming the Symfony application has a Dockerfile similar to the one created in the Symfony application containerization guide, the project’s Dockerfile will look like the following:

FROM ghcr.io/linkorb/php-docker-base:php8

COPY --chown=www-data:www-data --from=jakzal/phpqa /tools /tools

ENV PATH="$PATH:/tools:/tools/.composer/vendor/bin"

COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/

RUN install-php-extensions opcache

USER www-data:www-data

ARG COMPOSER_HOME=/tools/.composer

RUN cd /tools/.composer && \
   composer global bin phpstan require \
   phpstan/phpstan-phpunit \
   phpstan/phpstan-doctrine \
   phpstan/phpstan-symfony
  1. If you already have or intend to use a Dockerfile like the one above, copy the following code to the docker-compose.yml file, ensuring that app service is on the same tree level as the db service.

       app:
          build:
             context: .
             dockerfile: Dockerfile
          depends_on:
             - db
          ports:
             - "8000:80"
          volumes:
             .:/app
          environment:
             MARIADB_DATABASE: ${MARIADB_DATABASE:-app}
             MARIADB_USER: ${MARIADB_USER:-app}
             MARIADB_PASSWORD: ${MARIADB_PASSWORD:-!ChangeMe!}
             DATABASE_URL: "mysql://$MARIADB_USER:$MARIADB_PASSWORD@db:3306/$MARIADB_DATABASE?serverVersion=mariadb-10.7.8&charset=utf8mb4"
    

    The above instructs Compose to do the following:

    1. Create a service named app using the directives in the Dockerfile.

    2. Inform Docker that the app service depends on db service to function.

      db is the domain of the MariaDB service of the Compose application’s network in this example. If you give the database service a different name, please set the database domain to the name of that database service.

    3. Forward port 8000 of the host computer to port 8000 of the app service/container.

    4. Bind-mount the current working directory of the host computer to the /app directory of the app service/container.

    5. Set the following environment variables in the app service’s container. Use the default (hard-coded) values if different values are not provided.



    Note that the DATABASE_URL environment variable uses variable substitution. You must define the MARIADB_DATABASE, MARIADB_USER, and MARIADB_PASSWORD in the environmentfield (and optionally in the .env file) for this substitution to work.

    Doctrine DBAL configuration requires that you set the server_version property to the MariaDB version when using MariaDB. You may set this value in the config/packages/doctrine.yaml file of the Symfony application or the serverVersion (i.e. serverVersion=mariadb-10.7.8) query of the DATABASE_URL environment variable. The database server version in this example is the same as the MariaDB version in the db service. Please see Doctrine DBAL configuration for more information.

    Passing a Dockerfile to a Compose service as shown above allows for better control over the container image. You can, for example, extend and customize the Docker image before passing it to Compose.

    If you want to use a Docker image instead of a Dockerfile in a Compose service, please replace the build field (and its properties) in the app service above with image. For example, the following uses the LinkORB’s PHP 8 image:

       app:
          image: ghcr.io/linkorb/php-docker-base:php8
          #...
    
  2. Place the # symbol before the uncommented DATABASE_URL environment variable in the .env file as shown below to disable it.

    # DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8"
    

    Recall that the app service uses variable substitution to dynamically specify the DATABASE_URL environment variable in the docker-compose.yml file. That variable will be available in the Symfony application container, so you do not need to keep this one.

The docker-compose.yml file

The complete docker-compose.yml file should look like the code block below at this point.

version: '3'

services:
  db:
    image: mariadb:10.7.8
    restart: always
    ports:
      - "3306:3306"
    environment:
      MARIADB_DATABASE: ${MARIADB_DATABASE:-app}
      MARIADB_USER: ${MARIADB_USER:-app}
      MARIADB_PASSWORD: ${MARIADB_PASSWORD:-!ChangeMe!}
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD:-!ChangeMe!}

  app:
    build:
      context: .
      dockerfile: Dockerfile
    depends_on:
      - db
    ports:
      - "8000:80"
    volumes:
      - .: /app
    environment:
      MARIADB_DATABASE: ${MARIADB_DATABASE:-app}
      MARIADB_USER: ${MARIADB_USER:-app}
      MARIADB_PASSWORD: ${MARIADB_PASSWORD:-!ChangeMe!}
      DATABASE_URL: "mysql://$MARIADB_USER:$MARIADB_PASSWORD@db:3306/$MARIADB_DATABASE?serverVersion=mariadb-10.7.8&charset=utf8mb4"

Step 3: Run the Symfony application and MariaDB as Compose services

  1. Run the Compose application locally using the following command.

    docker compose --env-file=.env up
    

    You may view and follow the logs of an individual service in the Compose service with the following command.

    docker compose logs -f <service-name>
    

    E.g. To view the Symfony app’s logs, run the following command

    docker compose logs -f app
    
  2. Click the globe icon in the Local Address field of the Ports tab in VSCode’s integrated terminal to open the containerized application in the browser.

    The Symfony application container has access to a containerized MariaDB database now. You may open the locally stored project files, make changes, view the changes in a browser, or push the code to GitHub.

  3. Run the following command if you wish to stop the Compose application.

    docker compose down
    

Run binaries within a Compose service

You may run binaries available in a compose service/container using docker compose exec as follows:

  1. Ensure the Compose application is running or start it by running the following command in the terminal.

    docker compose --env-file up -d
    
  2. Run docker compose exec <service-name> <command>. The following example creates a database (using a Doctrine ORM) in the MariaDB service/container.

    docker compose exec app php bin/console doctrine:database:create
    

    Note that you must run the above commands at the root of the project directory for this to work.

Develop the containerized Symfony and MariaDB application in a devcontainer

A devcontainer requires an interactive user account. This, among other things, means you need to create and configure a custom user account in the Dockerfile used in the app service as shown below. Please see create a non-root user with shell access for more information.

FROM ghcr.io/linkorb/php-docker-base:php8

RUN groupadd devuser && \
    useradd -m -g devuser devuser && \
    chsh -s /bin/bash devuser && \
    chown -hR devuser:devuser /app && \
    sed -i 's/Listen 80/Listen 8000/g' /etc/apache2/ports.conf && \
    sed -i "s/*:80>/*:8000>/g" /etc/apache2/sites-enabled/000-default.conf

COPY --chown=devuser:devuser --from=jakzal/phpqa /tools /tools

ENV PATH="$PATH:/tools:/tools/.composer/vendor/bin"

COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/

RUN install-php-extensions opcache

USER devuser:devuser

ARG COMPOSER_HOME=/tools/.composer

RUN cd /tools/.composer && \
    composer global bin phpstan require \
    phpstan/phpstan-phpunit \
    phpstan/phpstan-doctrine \
    phpstan/phpstan-symfony

Assuming you have updated the Dockerfile, create a devcontainer as follows:

  1. Create a .devcontainer directory at the root of the project directory.

  2. Create a devcontainer.json file inside the .devcontainer directory.

  3. Add the following code to the devcontainer.json file

    {
       "name": "my-symfony-app",
       "dockerComposeFile": "../docker-compose.yml",
       "service": "app",
       "workspaceFolder": "/app",
       "postCreateCommand": "composer install --no-cache --no-interaction",
       "forwardPorts": [
          8000
       ],
       "customizations": {
          "vscode": {
             "extensions": [
                "whatwedo.twig"
             ]
          }
       },
       "remoteUser": "devuser",
       "shutdownAction": "stopCompose"
    }
    

    The above code instructs the devcontainer to do the following:

    1. Create a devcontainer for my-symfony-app using the ../docker-compose.yml file.
    2. Attach to the app service and run other (i.e. database) services in the background when the devcontainer starts.
    3. Forward incoming connections to port 8000 of the devcontainer to port 8000 of the app service.
    4. If the container is running in VSCode or a Codespace, install Twig extension.
    5. Set devuser (created in the Dockerfile) as the devcontainer’s user.
    6. Stop Docker Compose when the devcontainer or Codespace stops.

  4. Install the Dev Containers extension in VSCode.

  5. Click Reopen in container on the popup message at the bottom right of the screen or click the green button at the bottom left of VSCode’s window and select Reopen in container. Restart VSCode if Reopen in container does not appear on the screen.

Develop the containerized Symfony and MariaDB application in a Codespace

Please see Develop the containerized Symfony application within a GitHub Codespace for Codespace usage guidance.

Share the containerized application with the team

Please update the README.md file to document how to build, run, and develop the application before pushing the completed project to GitHub. A few points to note in the README.md file include:

  • Contributors need to run the following commands to install the application’s dependencies when working outside a devcontainer.

    1. Start the Compose services with this command.
      docker compose up
      
    2. Install the application’s dependencies.
      docker compose exec app composer install
      
  • To run their forks of the repository in a Codespace, contributors need to add Codespace secrets listed in Develop a containerized Symfony application within a GitHub Codespace to their forks.

About Docker