Docker ComposeとSymfonyを使って開発してみよう

May 24, 2021
執筆者
Oluyemi Olususi
寄稿者
Twilio の寄稿者によって表明された意見は彼ら自身のものです
レビュー担当者

Docker ComposeとSymfonyを使って開発してみよう

この記事はOluyemi Olususiこちらで公開した記事(英語)を日本語化したものです。

ソフトウェアをチーム間で開発すると、コードベースの管理や、開発者全員が問題なく使える開発環境の準備など、さまざまな困難が生じます。

Gitはバージョン管理において大きな役割を果たしており、誰もが問題なくコードベースを操作できるようになりましたが、作業環境が原因で混乱が生じるリスクがまだあります。

WindowsmacOSLinuxなど、開発者が好むオペレーティングシステムはさまざまであり、同じことがアプリケーションの導入環境にも当てはまります。

その結果、特定のエラーが発生すると、問題の原因を突き止めることが困難になります。エラーが1つのOSでのみ発生することが混乱に拍車をかける可能性もあります。開発者の間で有名なフレーズ、「It works on my machine」(私のマシンでは動作するのに)の状態につながります。

Dockerを開始する

小規模で軽量の実行環境であるコンテナにアプリケーションを作成することにより、アプリケーションの予測可能性が向上します。コンテナでは、基盤となるオペレーティングシステムカーネルを共有しますが、それ以外は互いから切り離して実行されます。

DockerをSymfonyプロジェクトに統合することにより、導入先に関係なく、アプリケーションの実行時に環境とその設定を常に同じにすることができます。

本稿では、SymfonyプロジェクトでDockerを使用する方法について紹介します。ここでは、NginxがWebサーバーとして使用され、PHP-FPMがPHPリクエストを処理し、MySQLがバックエンドデータベースになります。本稿では、著名な歴史家による有名な名言が表示されるアプリケーションを作成します。

必要条件

  • SymfonyTwigORM(特にDoctrine)の経験。
  • Dockerに関連する基本的な用語(コンテナイメージネットワークサービス)の知識。Jeff Hale氏は、これらの用語について、連載記事で分かりやすく解説しています。これらの用語についてあまり知らない場合は、ぜひ読んでみてください。
  • グローバルにインストールされているComposer
  • Docker Desktop
  • Symfony CLIツール

はじめに

最初に、symfony_dockerという名前の新しいディレクトリを作成し、次のコマンドで、そのディレクトリに切り替えます。

mkdir symfony_docker
cd symfony_docker

Docker Composeの設定を作成する

アプリケーションを構成するさまざまなコンテナが通信する必要があるため、Docker Composeを使用し、それらを定義します。symfony_dockerディレクトリのルートで次のコマンドを使用し、docker-compose.ymlという名前の新しいファイルを作成します。

touch docker-compose.yml

このファイルには、コンテナのビルド方法から、コンテナにアクセス可能なネットワークやボリュームまで、アプリケーションのスタックで作成するコンテナのすべての設定が保持されます。

docker-compose.ymlに以下の設定を追加します。

version: '3.8'

services:

versionはスキーマバージョンを参照します。servicesは、アプリケーションスタックを構成するコンテナのリストを定義します。servicesは、実際は単なる「本番環境のコンテナ」です。

次の各セクションでは、MySQLデータベース、PHP、Nginx Webサーバーのコンテナについて説明します。

データベースコンテナを定義する

データベースコンテナを定義するには、docker-compose.ymlで、次のように services要素を更新します。

services:
    database:
      container_name: database
      image: mysql:8.0
      command: --default-authentication-plugin=mysql_native_password
      environment:
        MYSQL_ROOT_PASSWORD: secret
        MYSQL_DATABASE: symfony_docker
        MYSQL_USER: symfony
        MYSQL_PASSWORD: symfony
      ports:
        - '4306:3306'
      volumes:
        - ./mysql:/var/lib/mysql

container_nameでは、Docker Composeでコンテナを生成するのではなく、実行時にコンテナの実際の名前を設定します。

imageでは、コンテナのビルド元のイメージ(ブループリント)をDockerに認識させることができます。この場合、バージョン8のMySQLを使用するため、mysql:8.0を指定します。

commandでは、ユーザーの認証にMySQLで使用される認証プラグインを指定します。environmentキーを使用すると、データベースの名前、ユーザー、パスワード、ルートユーザーのパスワードなどの環境変数を指定できます。

データベースに接続するポートも必要です。portsキーを使用して、ローカル開発マシンのポートを指定し、データベース接続の処理に使用されるコンテナのポートにマップします。

 

ポート4306は、MySQLサービスがすでにコンピューターで実行されている場合に指定します。

最後に、volumesキーを使用してボリュームを宣言します。Docker公式ドキュメントによると:

ボリュームは、Dockerコンテナが生成、使用するデータを保持するための推奨メカニズムです。

コンテナの破棄または再ビルド時にデータベースが失われないようにするために、ボリュームを宣言します。

PHPコンテナを定義する

データベースコンテナとは異なり、PHPコンテナを設定するには、追加の指示を指定する必要があります。これを行うには、DockerfileからPHPコンテナをビルドします。ルートディレクトリsymfony_dockerに、phpという名前のディレクトリを作成します。次に、symfony_docker/phpに、Dockerfileという名前のファイルを作成します。

Dockerfileには拡張子がありません。

mkdir php
touch php/Dockerfile

続いて、symfony_docker/php/Dockerfileに、以下を追加します。

FROM php:8.0-fpm
  
RUN apt update \
    && apt install -y zlib1g-dev g++ git libicu-dev zip libzip-dev zip \
    && docker-php-ext-install intl opcache pdo pdo_mysql \
    && pecl install apcu \
    && docker-php-ext-enable apcu \
    && docker-php-ext-configure zip \
    && docker-php-ext-install zip

WORKDIR /var/www/symfony_docker

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

RUN curl -sS https://get.symfony.com/cli/installer | bash
RUN mv /root/.symfony/bin/symfony /usr/local/bin/symfony
RUN git config --global user.email "you@example.com" \ 
    && git config --global user.name "Your Name"

symfony_docker/php/Dockerfileの最後の2行で、you@example.comをご自身のメールアドレスに、Your Nameを名前に置き換えてください。

PHP-FPMイメージからコンテナの雛形を作成することに加えて、以下を実行します。

  1. Symfonyが依存するPHP拡張機能をインストールします。
  2. コンテナの作業ディレクトリを/var/www/symfony_dockerに設定します。
  3. Composerをインストールします。
  4. Symfony CLIをインストールします。

次に、データベース設定の後に、以下のサンプルをdocker-compose.ymlに追加します。

  php:
    container_name: php
    build:
      context: ./php
    ports:
      - '9000:9000'
    volumes:
      - ./app:/var/www/symfony_docker
    depends_on:
      - database

ご覧のとおり、PHPコンテナは異なる方法で定義されています。イメージを指定する代わりに、ビルドコンテキストを指定します。このように、docker-composeコマンドを実行すると、php/Dockerfileに宣言された指示がコンテナのビルドに使用されます。

コンピューターのポート9000は、コンピューターのポートをMySQLデータベースのコンテナのポートにマップしたのと同じように、コンテナのポート9000にマップされます。

さらに、この場合もボリュームを宣言します。これは、コンテナで生成されたデータを保持するためです。この場合、SymfonyアプリケーションはPHPコンテナの/var/www/symfony_dockerディレクトリに作成され、プロジェクトのappディレクトリに保持されます。

最後に、depends_onキーを使用します。PHPとデータベースコンテナの間に依存関係が作成されることにより、PHPコンテナのにデータベースコンテナをビルドして開始するようにDockerに指示されます。

PHPコンテナを定義し、次のコマンドを使用して、プロジェクトのルートディレクトリにappディレクトリを作成します。これはコンテナで必要になります。

mkdir app

Nginxコンテナを定義する

Nginxコンテナをビルドする前に、サーバーのデフォルト設定を記述しましょう。プロジェクトのルートに、nginxという名前のディレクトリを作成し、その中に、次のコマンドを使用して、default.confという名前の設定ファイルを作成します。

mkdir -p nginx/default.conf

以下の設定をnginx/default.confに追加します。

server {
  
    listen 80;
    index index.php;
    server_name localhost;
    root /var/www/symfony_docker/public;
    error_log /var/log/nginx/project_error.log;
    access_log /var/log/nginx/project_access.log;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/index\\.php(/|$) {
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\\.php)(/.*)$;
        include fastcgi_params;

        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;

        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;

        internal;
    }

    location ~ \\.php$ {
        return 404;
    }

}

これは、Symfonyプロジェクトの実行に必要な基本的なNginx設定です。fastcgi_passで、PHPコンテナのポート9000を指定しています。ポート9000は、PHP-FPMがリクエストをリッスン(待機)するデフォルトのポートだからです。

次に、docker-compose.ymlにおいて以下のように、PHPコンテナの設定の後にNginxコンテナの設定を追加します。

  nginx:
    container_name: nginx
    image: nginx:stable-alpine
    ports:
      - '8080:80'
    volumes:
      - ./app:/var/www/symfony_docker
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - php
      - database

コンテナをビルドする

次のコマンドでコンテナをビルドします。

docker-compose up -d --build

コンテナがビルドされると、次のスクリーンショットのようにターミナルに表示されます。

コンテナのビルド後

Docker Desktopを開くと、次のスクリーンショットに示すように、新しく作成されたコンテナが表示されます。

新しく作成されたコンテナ

Symfonyアプリケーションを作成する

Symfonyアプリケーションを作成するには、PHPコンテナ内でターミナルを起動します。これには2つの方法があります。

  • Docker Desktopを使用する。symfony_dockerアプリケーションを展開すると、このアプリケーションを構成するコンテナを確認できます。以下のスクリーンショットで矢印で示されているボタンをクリックすると、CLIを開始できます。docker execコマンドが実行され、操作するためのターミナルが開きます。

Docker desktop
  • docker execコマンドを直接実行する。これを行うには、次のコマンドを実行します。
docker-compose exec php /bin/bash

選択した方式に関わらず、新しく開いたターミナルで次のコマンドを実行することにより、設定がSymfonyアプリケーションの要件を満たしていることを確認します。

symfony check:requirements

要件を満たしている場合は、ターミナルに次の出力が表示されます。

OK]                                             
Your system is ready to run Symfony projects(Symfonyプロジェクトを実行する準備ができました)

次に、以下のコマンドを実行して新しいSymfonyプロジェクトを作成します。

symfony new .

成功すると、ターミナルに以下のテキストが表示されます。

OK] Your project is now ready in /var/www/symfony_docker

アプリケーションが作成されたため、http://localhost:8080/に移動します。次の例のように、デフォルトのSymfonyインデックスページが表示されます。また、appディレクトリを見ると、Symfonyプロジェクトファイルが保持されていることが分かります。

Welcome to symfony画面

PHPコンテナのCLIに戻り、作成中のアプリケーションの開発依存関係を追加します。次のコマンドを実行します。

composer req --dev maker ormfixtures fakerphp/faker

データベース内に著名な歴史家による有名な名言を格納し、取得するには、ORM(正確にはDoctrine ORM)が必要になります。また、フロントエンドをレンダリングするにはTwigテンプレートエンジンが必要になります。次のコマンドを実行し、これらの依存関係を追加します。

composer req doctrine twig

次に、プロジェクトの作成中にSymfonyが生成した既存の.envファイルから.env.localファイルを作成します。これを行うには、次のコマンドを実行します。

cp .env .env.local

最後に、.env.localのデータベースパラメーターを更新し、アプリケーションがデータベースコンテナに接続できるようにします。ファイルの現在のDATABASE_URLエントリを以下に置き換えます。

DATABASE_URL="mysql://root:secret@database:3306/symfony_docker?serverVersion=8.0"

名言エンティティを作成する

次に、MySQLデータベースとの対話を処理するORMエンティティを作成します。これを行うには、Symfony Makerバンドルを使用し、次のコマンドを実行します。

symfony console make:entity Quote

一連の質問に対して次のように答えてください。

New property name (press <return> to stop adding fields):
  > quote

  Field type (enter ? to see all types) [string]:
  > text

  Can this field be null in the database (nullable) (yes/no) [no]:
  > no

  updated: src/Entity/Quote.php

  Add another property? Enter the property name (or press <return> to stop adding fields):
  > historian

  Field type (enter ? to see all types) [string]:
  > string

  Field length [255]:
  > 25

  Can this field be null in the database (nullable) (yes/no) [no]:
  > no

  updated: src/Entity/Quote.php

  Add another property? Enter the property name (or press <return> to stop adding fields):
  > year

  Field type (enter ? to see all types) [string]:
  > string

  Field length [255]:
  > 5

  Can this field be null in the database (nullable) (yes/no) [no]:
  > no

  updated: src/Entity/Quote.php

  Add another property? Enter the property name (or press <return> to stop adding fields):
  >

Quote.phpという名前のエンティティがsrc/Entityディレクトリに作成されます。src/Entity/Quote.phpを開き、次に示すようにコンストラクタ関数を追加します。

public function __construct($quote, $historian, $year) {
    $this->quote = $quote;
    $this->historian = $historian;
    $this->year = $year;
}

次のコマンドを使用し、データベースを更新するための移行(migration)を作成します。

symfony console make:migration

次のコマンドを使用し、移行を実行します。

symfony console doctrine:migrations:migrate

プロンプトが表示されたら「yes」を選択します。「quote」という名前の新しいテーブルでデータベースが更新されます。

作成されたことを確認するには、新しいターミナルウィンドウを開き、symfony_dockerディレクトリ内から次のコマンドを実行します。

docker-compose exec database /bin/bash

ターミナルが開いたら、次のコマンドを使用してデータベースに接続します。

mysql -u root -p symfony_docker

プロンプトが表示されたら、MYSQL_ROOT_PASSWORDを入力します。接続するデータベースを指定したため、次のコマンドを使用してテーブルに対してクエリを実行できます。

> show tables;

次のような出力が表示されます。

mysql> show tables;
+-----------------------------+
| Tables_in_symfony_docker    |
+-----------------------------+
| doctrine_migration_versions |
| quote                       |
+-----------------------------+
2 rows in set (0.00 sec)

名言フィクスチャを作成する

次に、生成された一連の名言をデータベースに読み込み、アプリの準備ができたとき、名言を取得して表示できるようにします。これを行うには、PHPコンテナで、次のコマンドを実行します。

symfony console make:fixture QuoteFixture

フィクスチャはsrc/DataFixtures/QuoteFixture.phpにあります。ファイルを開き、以下のように更新します。

<?php

namespace App\DataFixtures;

use App\Entity\Quote;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
use Faker\Factory;

class QuoteFixture extends Fixture {

    private $faker;

    public function __construct() {

        $this->faker = Factory::create();
    }

    public function load(ObjectManager $manager) {

        for ($i = 0; $i < 50; $i++) {
            $manager->persist($this->getQuote());
        }
        $manager->flush();
    }

    private function getQuote() {

        return new Quote(
            $this->faker->sentence(10),
            $this->faker->name(),
            $this->faker->year()
        );
    }
}

データベースにフィクスチャを読み込むには、PHPコンテナで次のコマンドを実行します。

symfony console doctrine:fixtures:load

データベースにフィクスチャを読み込むように求められたら、「yes」と応答します。その後、データベースコンテナで次のコマンドを実行し、名言が読み込まれたことを確認します。

> SELECT * FROM quote

テーブルは次のようになっています。

mysql> SELECT * FROM quote limit 10;
+----+----------------------------------------------------------------------------------------------------+----------------------+------+
| id | quote                                                                                              | historian            | year |
+----+----------------------------------------------------------------------------------------------------+----------------------+------+
|  1 | Aut eaque aut quos autem incidunt ut est quod tempore sed aut placeat.                             | Einar Lebsack DDS    | 2001 |
|  2 | Eum autem sed aut quos impedit cupiditate harum voluptatem aut qui qui sunt ad.                    | Aliza Morissette     | 1990 |
|  3 | Deserunt consequatur et sunt architecto enim quia deleniti consectetur est reprehenderit.          | Zora Bailey          | 1973 |
|  4 | Pariatur adipisci voluptatem rerum id adipisci doloremque porro maxime unde placeat ad autem sint. | Dallin Erdman        | 1974 |
|  5 | Nobis magni eius voluptatibus blanditiis sequi praesentium aut aperiam et.                         | Pearlie Cremin PhD   | 1982 |
|  6 | Aperiam labore cum delectus aut consequatur animi in.                                              | Stone Harvey         | 2008 |
|  7 | Reiciendis non quia libero omnis quis quae quo aut odit tempora aut.                               | Aurore Graham        | 1988 |
|  8 | Earum aliquid quia reiciendis repudiandae non consequatur aliquid.                                 | Lyric Towne          | 1977 |
|  9 | Eos illo et unde sint esse tenetur.                                                                | Mr. Demarcus Klein V | 1988 |
| 10 | Maxime soluta veniam qui debitis sit maiores sint dolores culpa architecto aliquam est facere.     | Francesca O'Connell  | 1970 |
+----+----------------------------------------------------------------------------------------------------+----------------------+------+
10 rows in set (0.00 sec)

名言コントローラーを作成する

次に、新しいコントローラーを作成します。このコントローラーでは、ORMエンティティを使用してデータベースから名言を取得し、ビューに表示して閲覧できるようにします。これを行うには、PHPコンテナで、次のコマンドを実行します。

symfony console make:controller QuoteController

コマンドが完了すると、QuoteController.phpという名前の新しいコントローラーがsrc/Controllerディレクトリに作成されます。ファイルを開き、以下のように更新します。

<?php

namespace App\Controller;

use App\Repository\QuoteRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\\Annotation\Route;

class QuoteController extends AbstractController {
    #[Route('/', name: 'index')]
    public function index(
        QuoteRepository $quoteRepository
    )
    : Response {

        return $this->render(
            'quote/index.html.twig',
            [
                'quotes' => $quoteRepository->findAll(),
            ]
        );
    }
}

ビューのスタイルを設定する

最後に、ビューの表示を整理し、読みやすくします。これを行うには、Bootstrapを使用してスタイルを設定します。以下のようにapp/templates/base.html.twigを更新します。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link
            href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css"
            rel="stylesheet"
            integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6"
            crossorigin="anonymous">

    <title>{% block title %}Welcome!{% endblock %}</title>
    {% block stylesheets %}
    {% endblock %}

    {% block javascripts %}
    {% endblock %}
</head>
<body>
{% block body %}{% endblock %}
<script
        src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf"
        crossorigin="anonymous">
</script>
</body>
</html>

次に、app/templates/quote/index.html.twigを開き、以下のように編集します。

{% extends 'base.html.twig' %}

{% block title %}Quotes{% endblock %}

{% block body %}
    <style>
        .wrapper {
            margin: 1em auto;
            width: 95%;
        }
    </style>

    <div class="wrapper">
        <h1>Great Quotes</h1>
        <table class="table table-striped table-hover table-bordered">
            <thead>
            <tr>
                <th scope="col">#</th>
                <th scope="col">Quote</th>
                <th scope="col">Historian</th>
                <th scope="col">Year</th>
            </tr>
            </thead>
            <tbody>
            {% for quote in quotes %}
                <tr>
                    <td>{{ loop.index }}</td>
                    <td>{{ quote.quote }}</td>
                    <td>{{ quote.historian }}</td>
                    <td>{{ quote.year }}</td>
                </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
{% endblock %}


アプリケーションをテストする

完成したアプリケーションをテストします。インデックスページをリロードし、データベースに保存されている有名な名言を表示します。

名言リスト

Dockerを使用してSymfonyプロジェクトを設定する方法は以上です

このチュートリアルでは、イメージとDockerfileからコンテナをビルドするだけでなく、相互に通信することもできました。これにより、Symfonyアプリケーションとデータベースを個別のコンテナで実行できました。

同じ仕様でコンテナをビルドすることにより、開発チームは同じコードベースだけでなく、同じ環境で作業できます。

このチュートリアルの全コードベースは、GitHubで入手できます。ぜひご確認ください。コーディングを楽しみましょう!

Oluyemiは、電気通信工学のバックグラウンドを持つテクノロジー愛好家です。ユーザーが直面する日々の問題を解決することに強い関心を持ち、プログラミングの道に進んで以来、Webとモバイルの両方のソフトウェア開発で問題解決能力を磨いてきました。

Oluyemiは、知識の共有に情熱を注ぐフルスタックのソフトウェアエンジニアであり、ブログで多数の技術記事とコンテンツをインターネットに公開しています。テクノロジーにも精通しており、趣味は新しいプログラミング言語とフレームワークを試すことです。