Tech Racho エンジニアの「?」を「!」に。
  • 開発

Docker イメージを利用したローカル開発環境向けメールサーバ構築のすゝめ

ebi です。
弊社では Rails だけでなく、 WordPress 等を利用した PHP 環境、WordPress 等のフレームワークを利用するまでもなく、素の PHP で構成されるページを作成、保守することもあります。

往々にして LP だったり、コーポレートサイトやサービスサイトが中心で、サイト上からのメールの送信を伴う問い合わせフォームがセットとなっていることが多いです。

Rails だとメール送信は Action Mailer を利用して、メール受信は letter_opener 等を利用すればすぐにそれっぽい画面で確認できて便利だな、と思っていたのですが、そこそこ前から Rails 以外のプロジェクトでも、 Docker を利用して letter_opener 相当の環境を手元に用意しているのが便利なので今回はそれを紹介します。

これまでの話

ところで、そもそもこれまではどうしてたのよ、な話を先にしておくと

Gmail アカウント等を利用し、外部の SMTP サーバーを経由する設定をローカル開発環境向けに書く

実際のメールサーバを使って実際のメールを送信できる、至極真っ当な手段だとは思うのですが、一つ大きな問題があって、 本番設定のメール送信先設定を引き継いだままだと本番設定の宛先にメールを送ってしまいます。

もっと分かりやすく言うと、うっかりお客さん宛に手元のローカル開発環境からのテスト送信のメールを送ってしまう事故が発生する可能性があるんですね(弁明しておくと、通常はこう言う危ない設定とかは真っ先に確認して潰すので大丈夫なのですが、事故が起きかねないリスクをそれなりに孕んでいるという話です)。

そのため、対策として以下などに取り組んだりしています。

1. 自前でメール送信のコードを書いている場合は、送信先の設定をハードコードしない

  • 定数( .gitignore されるファイルに置くといい )を参照するようにし、 GitLab 等に共有する開発環境向けのコードではデフォルトを実際にメールが届かないサンプルの宛先にしておく。
  • 本番環境上のサーバ内の定数定義を行うファイル中でのみ、有効な設定を記述する。

2. WordPress 等のプラグインを利用したメールフォームの場合は、DB 上に保存されているメール送信先の設定変更を初期の環境構築の一環でやる

  • サイト URL のドメイン置換等の初期構築を行う一環で、同様にメールアドレスのデータを含む設定テーブル内でのメールアドレスの置換を行うようにします(本題からズレるので詳細は割愛)。
  • 安全化のためのメールアドレス置換までを含めた初期構築用のスクリプトを共有し、皆がそれを使うことで安全なローカル開発環境の構築を担保する。
    (あからさまに個人情報を含むテーブルはそもそも dump する対象には含めない、でも事足りるのですが管理者アカウント系はあった方がよいのでこうする)

ステージング環境向けにブラックホールメールサーバが適用されている

ので、そっちの環境を積極的に利用していきましょう。と言う場合です。今回作ろうとしている安全にメールが確認できる環境が、ステージング環境に既にあるケースですね。

ブラックホールメールサーバの中身の話は この記事この記事 をご覧ください(今気付いたけど、 BPS で現在運用されている yaasita さんのブラックホールメールサーバは Docker 版 が公開されていますね)。

なお、弊社社員の場合は、このサーバが社内ネットワーク中で運用されているので、ローカル開発環境向けの設定でこのサーバを利用することもできるようになっているはずです。

事前準備

記事の本題に入っていきます。
後半の内容に則したものになりますが、 GitHub にも置いておく ので、そちらも適宜ご活用ください。

サンプルコード例

メール送信用のコードについて詳細を語るのは本筋ではない(書くならちゃんと書かないと有害記事になっちゃうからね!)ので、以下の滅茶簡略化コードで進めていきます。mail コマンドの例は リファレンス から拝借。

<h1>Techracho メール送信サンプル</h1>
<form action="./techracho-sample-mail.php">
  <input type="submit" value="送信する" />
</form>
  • techracho-sample-mail.php
<?php
$to      = 'nobody@example.com';
$subject = 'the subject';
$message = 'hello';
$headers = 'From: webmaster@example.com';

mail($to, $subject, $message, $headers);
?>
<h1>送信できたかな……</h1>

また、環境構築を進めていく際には phpinfo() で適時現状を確認していきます。主に sendmail_path

  • info.php
<?php
phpinfo();

Docker で立ち上げるメールサーバ

今回は MailDev を採用した例を書きますが、もちろん、その他サービスの利用に応用してもらっても特に問題ないはずです。
また、 MailDev 自体は本来 node 環境だけでも動くもののようです。このようにメールサーバの立ち上げに、必ずしも Docker が必要なわけではありませんので、各人の好みや環境に合わせて何を使うか選択していくのが良いと思います。

参考記事:

サンプル例その1: XAMPP 環境

僕が愛用する Windows で Web 開発の基本の PHP 環境を構築する際はやはり XAMPP にお世話になっていたので、まずは XAMPP を例にしてサンプル例を進めていきます。
XAMPP だと mailtodisk を使う方法もあったりはするのですが、実際のメールサーバを使っての送信により近い環境を作りたいですよね。

1. 初期状態

  • phpinfo

送信結果

Warning: mail(): Failed to connect to mailserver at "localhost" port 25, verify your "SMTP" and "smtp_port" setting in php.ini or use ini_set() in C:\xampp\htdocs\techracho-sample-mail.php on line 8

エラーメッセージが出ていてダメそうです。

mail() を実行しようとはしていますが、ローカルのメールサーバ設定が不適切な状態なので sendmail_path を調整してみます。

2. sendmail_path を繋げる

先ほどのエラーメッセージ中で php.ini を調整するように言われているので、 C:\xampp\php\php.inisendmail_path を検索して適切な定義場所を探します。見つけたら sendmail_path = "C:\xampp\sendmail\sendmail.exe -t" とします。

設定追加後、 Apache を再起動して設定を適用させます。

  • phpinfo

sendmail_path が更新されました。

送信結果

先ほどのエラーメッセージは表示されなくなりました!

しかし、宛先を実在するメールアドレスにしてもメールは届きません(本当は sendmail が出力する error_logfiledebug_logfile を確認したかったのですが、この時点では出力されず、あとで再度確認すると出力されるようになっていた。いまいち分からない…… 🤔 )。

今回の主役。送信用メールサーバを用意しましょう。

3. MailDev サーバを立ち上げる

  • docker run -d -p 1080:80 -p 1025:25 -h maildev --name maildev djfarrelly/maildev を実行します。

ところで言い忘れましたが、この記事は Windows 単体環境で記事を書いているので Docker の利用には、 Docker Machine ( Docker Toolbox )を使って進めていきます。

そのため、立ち上げた Docker のメールサーバは docker-machine が動いている IP 上(以下の例では 192.168.99.100 )で動いていることに注意しましょう。

C:\Users\ebi>docker-machine ls
NAME      ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER        ERRORS
default   *        virtualbox   Running   tcp://192.168.99.100:2376           v17.05.0-ce
  • 先ほど docker run コマンドで、 1080 番ポートと 80 番ポートを繋げているので、 http://192.168.99.100:1080 等の URL にアクセスすると MailDev サーバ上に届いたメールを確認することができます。

以後、この画面を開いてメールが正しく送信できているかを確認することになります。

送信結果

変化なし

MailDev 画面

さて、早速見に行ってみるのですが……変化なし!

  • 落ち着いて sendmail の設定を見直してみます。 C:\xampp\sendmail\sendmail.ini を確認すると、以下の内容が怪しそうです。
smtp_server=mail.mydomain.com
; smtp port (normally 25)
smtp_port=25

4. sendmail の設定を MailDev を利用する形に変更する

  • 先ほども言った通り、今回は Windows の Docker Machine 上で動いているのでメールサーバの IP は Docker Machine 上を指定する必要があって、ポート番号 1025 から MailDev コンテナのポート番号 25 を繋いでいるので、 docker-machine ls で表示される IP アドレスに従って
smtp_server=192.168.99.100

smtp_port=1025

とします。

再度リベンジすると……今度は届いていました! \(^o^)/

サンプル例その2: docker-compose 環境

php や mysql 環境とかのバージョンを色々制御するのには Docker がやはり便利なので最近は PHP 環境も docker-compose にお世話になることが多いです。と言うことでこっちの例の方が普段から良くお世話になっている例になります。
書くの疲れたので、重複する内容は飛ばしつつさっくりと行きます。

1. docker-compose を用意する

PHP を使うので公式の Apache 入り Docker イメージを使うといいでしょう。それっぽい docker-compose.yml を書いてここから始めていきます。 docker-compose up -d してみます。

  • docker-compose.yml
version: '3'
services:
  web:
    image: php:7.3-apache
    volumes:
      - .:/var/www/html
    ports:
      - "3080:80"

  mail:
    image: djfarrelly/maildev
    ports:
      - "1080:80"

docker-compose は問題なく立ち上がってそう。

C:\Users\ebi\Desktop\techracho-mail-sample>docker-compose ps
           Name                         Command               State                     Ports
----------------------------------------------------------------------------------------------------------------
techrachomailsample_mail_1   bin/maildev --web 80 --smtp 25   Up      0.0.0.0:1025->25/tcp, 0.0.0.0:1080->80/tcp
techrachomailsample_web_1    docker-php-entrypoint apac ...   Up      0.0.0.0:3080->80/tcp

メインの WEB サーバとの接続は 3080 番ポートにしたので、http://192.168.99.100:3080/techracho-sample.html 等で無事アクセスできました。メール送信検証してみます。

ダメです。空です。

  • phpinfo

sendmail_path 微妙そうですが、デフォルトの /usr/lib/sendmail があれば大丈夫そうな気もする……?

そこで、現在使用している Apache サーバを確認してみると、sendmail がまずなさそう。

C:\Users\ebi\Desktop\techracho-mail-sample>docker-compose exec web bash
root@2f7e813ba418:/var/www/html# which sendmail
root@2f7e813ba418:/var/www/html# cd /usr/lib/
root@2f7e813ba418:/usr/lib# ls -l
total 84
drwxr-xr-x  4 root root 4096 Jun 11 01:46 apache2
drwxr-xr-x  5 root root 4096 Jun 10 00:00 apt
drwxr-xr-x  2 root root 4096 Apr  2 19:05 cgi-bin
drwxr-xr-x  2 root root 4096 Jun 11 01:35 compat-ld
drwxr-xr-x  3 root root 4096 Jun 10 00:00 dpkg
drwxr-xr-x  2 root root 4096 Jun 11 01:35 file
drwxr-xr-x  4 root root 4096 Feb 14  2018 gcc
drwxr-xr-x  2 root root 4096 Jun 11 01:35 gold-ld
drwxr-xr-x  2 root root 4096 Jun 11 01:35 ldscripts
drwxr-xr-x  3 root root 4096 Feb  6 21:17 locale
drwxr-xr-x  5 root root 4096 Jun 11 01:46 mime
-rw-r--r--  1 root root  236 Mar 28 09:12 os-release
-rw-r--r--  1 root root   17 May  1  2016 pkg-config.multiarch
drwxr-xr-x  2 root root 4096 May  1  2016 pkgconfig
drwxr-xr-x  3 root root 4096 May 21  2017 python2.7
drwxr-xr-x  3 root root 4096 May 21  2017 python3
drwxr-xr-x  2 root root 4096 Mar 19  2017 sasl2
drwxr-xr-x  3 root root 4096 Jun 11 01:35 ssl
drwxr-xr-x  2 root root 4096 Oct 30  2016 tar
drwxr-xr-x  2 root root 4096 Jun 11 01:54 tmpfiles.d
drwxr-xr-x 15 root root 4096 Jun 11 01:54 x86_64-linux-gnu
  • sendmail コマンド用のライブラリを入れるところから必要なようです。使用した PHP Docker のリポジトリに近い Issue 等を見るとこの例この例 では、 ssmtp を入れていたのでこれを真似していきます。

2. web コンテナに sendmail を追加する

  • ひとまずさっきの流れに乗って、コンテナ上で直接追加してみます。
root@2f7e813ba418:/usr/lib# apt-get update && apt-get install -y ssmtp
Ign:2 http://cdn-fastly.deb.debian.org/debian stretch InRelease
Get:1 http://security-cdn.debian.org/debian-security stretch/updates InRelease [94.3 kB]
Get:4 http://cdn-fastly.deb.debian.org/debian stretch-updates InRelease [91.0 kB]
Get:3 http://security-cdn.debian.org/debian-security buster/updates InRelease [39.1 kB]
Get:6 http://security-cdn.debian.org/debian-security stretch/updates/main amd64 Packages [497 kB]
Get:8 http://cdn-fastly.deb.debian.org/debian stretch-updates/main amd64 Packages [27.2 kB]
Get:5 http://cdn-fastly.deb.debian.org/debian buster InRelease [163 kB]
Get:7 http://cdn-fastly.deb.debian.org/debian buster-updates InRelease [46.8 kB]
Get:10 http://security-cdn.debian.org/debian-security buster/updates/main amd64 Packages [1132 B]
Get:9 http://cdn-fastly.deb.debian.org/debian stretch Release [118 kB]
Get:11 http://cdn-fastly.deb.debian.org/debian buster/main amd64 Packages [7902 kB]
Get:12 http://cdn-fastly.deb.debian.org/debian stretch Release.gpg [2434 B]
Get:13 http://cdn-fastly.deb.debian.org/debian stretch/main amd64 Packages [7082 kB]
Fetched 16.1 MB in 6s (2296 kB/s)
Reading package lists... Done
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  libgnutls-openssl27
The following NEW packages will be installed:
  libgnutls-openssl27 ssmtp
0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
Need to get 239 kB of archives.
After this operation, 236 kB of additional disk space will be used.
Get:1 http://cdn-fastly.deb.debian.org/debian stretch/main amd64 libgnutls-openssl27 amd64 3.5.8-5+deb9u4 [185 kB]
Get:2 http://cdn-fastly.deb.debian.org/debian stretch/main amd64 ssmtp amd64 2.64-8+b2 [54.2 kB]
Fetched 239 kB in 0s (371 kB/s)
debconf: delaying package configuration, since apt-utils is not installed
Selecting previously unselected package libgnutls-openssl27:amd64.
(Reading database ... 13114 files and directories currently installed.)
Preparing to unpack .../libgnutls-openssl27_3.5.8-5+deb9u4_amd64.deb ...
Unpacking libgnutls-openssl27:amd64 (3.5.8-5+deb9u4) ...
Selecting previously unselected package ssmtp.
Preparing to unpack .../ssmtp_2.64-8+b2_amd64.deb ...
Unpacking ssmtp (2.64-8+b2) ...
Processing triggers for libc-bin (2.24-11+deb9u4) ...
Setting up libgnutls-openssl27:amd64 (3.5.8-5+deb9u4) ...
Setting up ssmtp (2.64-8+b2) ...
debconf: unable to initialize frontend: Dialog
debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 76.)
debconf: falling back to frontend: Readline
Processing triggers for libc-bin (2.24-11+deb9u4) ...
  • 再度確認すると、今度は sendmail 使えそうです。
root@2f7e813ba418:/usr/lib# which sendmail
/usr/sbin/sendmail
  • しかし、リベンジした感じはまだダメそうです。面倒臭がらずちゃんと sendmail_path を定義して怪しそうなところを潰していきます。

3. sendmail_path を繋げる

  • php.ini を作成して、 sendmail_path = "/usr/sbin/sendmail -t -i" とします。
  • phpinfo を確認して、上記設定が適用されていることを確認します。

  • リベンジしたら無事メールが届いていることが確認できました。

補足

  • この例だと、 SMTP サーバの設定が特に含まれていません。これは、 MailDev サーバに適当に名付けたホスト名 mail がデフォルトの SMTP サーバ設定と一致していたからです。
  • そのため、別のホスト名 mail_sample 等に変えてしまうと途端にメールが届かなくなってしまいます。この場合は、 web コンテナ上の /etc/ssmtp/ssmtp.conf の設定を mailhub=mail_sample に変更し、使用する SMTP サーバを正しく設定することで再度メールが届くようになります。

仕上げに

ssmtp のインストール等は Dockerfile として定義しておいた方がいい気がします。 vim とか欲しい人はそう言うのも入れるといいかもです。

  • Dockerfile
FROM php:7.3-apache

RUN apt-get update; apt-get install -y ssmtp
  • docker-compose.yml
version: '3'
services:
  web:
    build: .
    volumes:
      - .:/var/www/html
    ports:
      - "3080:80"

  mail:
    image: djfarrelly/maildev
    ports:
      - "1080:80"

サンプル例その3:その他のメールサーバ向け Docker イメージを利用

おまけです。すぐ終わります。
冒頭にも言った通り、メールサーバ向けの Docker イメージとして MailDev を使ったのに特に拘りはないので。
先ほどの docker-compose.yml の例で、使用するメールサーバ用のコンテナを、発掘した yaasita さんのブラックホールメールサーバイメージ に置き換えてみましょう。

  • docker-compose.yml
version: '3'
services:
  web:
    build: .
    volumes:
      - .:/var/www/html
    ports:
      - "3080:80"

  mail:
#    image: djfarrelly/maildev
    image: yaasita/docker_blackhole_mail_server
    ports:
      - "1080:80"

とすると……
こちらの Docker イメージでも問題なく動作することが確認できました! 😌

今日の記事はこんなところで終わりです。
皆さんも、おすすめのメールサーバ向け Docker イメージや環境構築手法があれば教えてください。


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。