こんにちは
今回は表題のとおり サーバー上に直接ホストしている Apache + PHP-FPM コンテナから、同一サーバー上に直接ホストしているPostfixを利用して別サーバーへメールを送信してみました。
方法としては、デフォルトではPHP-FPMコンテナ上からサーバー上のPostfixへ送信するための設定が難しいため、msmtp を利用して実装しました。
なお、やってみた背景としては、純粋にPHPコンテナからサーバー上のPostfixに対してどのようにしてメールを送信するのか疑問に思ったからとなります。
それでは、やっていきましょう
※注意点
・基本的なメール送信とDockerの技術については説明しません
→ 今回は Dovecotを利用せずに、Postfixを利用したMTA間の転送でメールの受信を確認します
▼前提
環境:vagrant(プライベート環境)
OS:AlmaLinux9
▼構成と送信流れ
サーバー1:Apache + PHP-FPMをコンテナでホスト、Postfixでメール送信、ドメイン名は dockermail01.mailtest.com
サーバー2:Postfixでメール受信、ドメイン名は mailclient01.mailtest.com
ブラウザ上からアクセス
↓
サーバー1のApacheコンテナでリクエスト受信
↓
サーバー1のPHP-FPMコンテナへローカルプロキシ
↓
ブラウザ上から宛先などの情報を入力画面を表示し、情報を入力後、送信ボタンを押下
↓
サーバー1のPostfix へメール送信リクエストを送信
↓
サーバー2のPostfix へメール配送
▼手順
1. サーバー上でPostfixのセットアップ
まずは、サーバー1と2でPostfixをセットアップします
なお、具体的な手順はこちらの記事を参考にして進めますが、下記2点は修正してください
・ドメイン名を example.com 以外にする
→ メール送信の際にNull MX のエラーとなるため
・main.cf へ ignore_mx_lookup_error = yes を追記する
→ smtp_host_lookup = native を追記しており、DNS名前解決よりも/etc/hosts 設定が優先されるはずですが、なぜか私の環境ではDNS名前解決が優先されたため
また、今回はローカルでメールの送受信をするため、/etc/hosts は下記を追記しました
192.168.33.95 dockermail01.mailtest.com 192.168.33.96 mailclient01.mailtest.com
2. サーバー1でDockerとdocker-compose のセットアップ
次はApache + PHP-FPMコンテナをホストするために、サーバー1で Docker をセットアップします
なお、具体的な手順は以前書いたこちらの記事を参考にしてください
3. Apache + PHP-FPM コンテナの起動
各Dockerファイルを準備します
・全体の構成
.
├── app
│ ├── Dockerfile
│ ├── conf
│ │ ├── disable_display_error.ini
│ │ ├── msmtprc
│ │ ├── php.ini
│ │ └── zz-docker.conf
│ └── src
│ ├── index.php
│ ├── mail_form.php
│ └── test_mail.php
├── docker-compose.yml
└── web
├── Dockerfile
└── conf
├── dockermail01.mailtest.com.conf
└── httpd.conf
・docker-compose.yml
version: "3.9"
services:
web01:
build:
context: ./
dockerfile: web/Dockerfile
volumes:
- ./app/src:/var/www/vhosts/dockermail01.mailtest.com/public_html
ports:
- "80:80"
app01:
build:
context: ./
dockerfile: app/Dockerfile
volumes:
- ./app/src:/var/www/vhosts/dockermail01.mailtest.com/public_html
- ./app/conf/msmtprc:/etc/msmtprc
restart: always
depends_on:
- web01
extra_hosts:
- "dockermail01.mailtest.com:host-gateway"
・app/Dockerfile
FROM php:8.0.30-fpm
#ブラウザ上にphpのエラー内容を出力させない場合は下記を有効化する
COPY app/conf/disable_display_error.ini $PHP_INI_DIR/conf.d/
#php.iniの設置
COPY app/conf/php.ini $PHP_INI_DIR/conf.d/php.ini
#php-fpm用confファイルコピー
COPY app/conf/zz-docker.conf /usr/local/etc/php-fpm.d/
#msmtp用confファイルの設置
COPY app/conf/msmtprc /etc/msmtprc
#コンテナ実行ユーザーとグループをrootから変更
RUN groupmod -g 1000 www-data
RUN usermod -u 1000 www-data
#ドキュメントルート作成
# DEBIAN_FRONTEND=noninteractiveがないとmsmtpがインタラクティブで立ち上がってしまいイメージビルドが進まなくなる
RUN apt-get update -y && \
DEBIAN_FRONTEND=noninteractive apt-get install -y vim less git msmtp msmtp-mta && \
docker-php-ext-install pdo_mysql && \
mkdir -p /var/www/vhosts/dockermail01.mailtest.com/public_html
COPY ./app/src /var/www/vhosts/dockermail01.mailtest.com/public_html
#ドキュメントルートの権限変更
RUN chown -R www-data:www-data /var/www/vhosts/dockermail01.mailtest.com/public_html
#ログファイルの権限変更
RUN chown -R www-data:www-data /var/log/
# ユーザー切り替え
USER www-data
#作業ディレクトリ設定
WORKDIR /var/www/vhosts/dockermail01.mailtest.com/public_html
#ポート開放
EXPOSE 9000
→ 注意点として、DEBIAN_FRONTEND=noninteractive を指定しないと、msmtpが入力待ちとなり、イメージビルドが止まります
・app/conf/disable_display_error.ini
display_errors = Off log_errors = true
→ PHPのエラーログを出力させます
・app/conf/msmtprc
defaults auth off tls off logfile /var/log/msmtp.log account default host dockermail01.mailtest.com port 25 from user01@dockermail01.mailtest.com
→ msmtp の設定ファイルです
・app/conf/php.ini
sendmail_path = "/usr/bin/msmtp -t"
→ 基本的にはデフォルトでOKですが、下記のみ msmtp ように修正が必要です
・app/conf/zz-docker.conf
[global] daemonize = no [www] listen = 9000 pm = dynamic pm.max_children = 50 pm.start_servers = 5 pm.min_spare_servers = 5 pm.max_spare_servers = 35
→ php-fpmのチューニング用ファイルです(なくても大丈夫ですが、その場合はDockerfileを修正してください
・app/src/index.php
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>This is an index page</h2>
<a href="./mail_form.php">メール送信フォーム</a>
</body>
</html>
→ indexファイルとなり、ブラウザから最初にアクセスした際に表示されます
・app/src/mail_form.php
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<form action="./test_mail.php" method="post">
<p>送り先</p><input type="text" name="to">
<p>件名</p><input type="text" name="title">
<p>メッセージ</p><textarea name="content" cols="60" rows="10"></textarea>
<p><input type="submit" name="send" value="送信"></p>
</form>
<a href="./index.php">戻る</a>
</body>
</html>
→ メールを送信するために必要な情報を入力するための画面です
・app/src/test_mail.php
<?php
//変数の初期化
$to = $title = $content = $headers = '';
mb_language("Japanese");
mb_internal_encoding("UTF-8");
$to = $_POST['to'];
$title = $_POST['title'];
$content = $_POST['content'];
$from = "user01@dockermail01.mailtest.com";
$header = 'From: ' . $from ;
if(mb_send_mail($to, $title, $content, $header, '-f ' . $from))
{
echo "メール送信成功です";
} else {
echo "メール送信失敗です";
}
?>
→ メールを送信処理を実施するプログラムとなり、ブラウザ上から受け取ったPOSTの中身を渡しています
・web/Dockerfile
#ローカルでコンバートしたイメージを指定
FROM httpd:latest
#デフォルトのconfファイルでは下記パスのみ設定されているので、下記以外のパスでドキュメントルートを作成する場合は <Directory> で別途ドキュメントルートパスを指定する必要あり
#<Directory "/usr/local/apache2/htdocs">
#デフォルトで下記includeはコメントアウトされているので、デフォルトのconfを書き換える必要がある
# Virtual hosts
#Include conf/extra/httpd-vhosts.conf
#デフォルトのconfファイルを上書き
COPY web/conf/httpd.conf /usr/local/apache2/conf/httpd.conf
#追加のvhost用confファイルをコピー
COPY web/conf/dockermail01.mailtest.com.conf /usr/local/apache2/conf/extra
#コンテナ実行ユーザーとグループをrootから変更
RUN groupmod -g 1000 www-data
RUN usermod -u 1000 www-data
RUN apt-get update -y && apt-get install less
#ドキュメントルート作成
RUN mkdir -p /var/www/vhosts/dockermail01.mailtest.com/public_html
#appコンテナ配下のソースコードをコピー
COPY ./app/src /var/www/vhosts/dockermail01.mailtest.com/public_html
#ドキュメントルートとPIDの権限変更
RUN chown -R www-data:www-data /var/www/vhosts/dockermail01.mailtest.com/public_html && \
chown -R www-data:www-data /usr/local/apache2/logs
# ユーザー切り替え
USER www-data
#作業ディレクトリ設定
WORKDIR /var/www/vhosts/dockermail01.mailtest.com/public_html
#ポート開放
EXPOSE 80
・web/conf/httpd.conf
→ 基本的には特に触ることはないですが、何か追加モジュールがある場合ように設定してます(なくても大丈夫ですが、その場合はDockerファイルを修正してください
・web/conf/dockermail01.mailtest.com.conf
<VirtualHost *:80>
ServerAdmin test@example.com
ServerName dockermail01.mailtest.com
DocumentRoot /var/www/vhosts/dockermail01.mailtest.com/public_html
<Directory /var/www/vhosts/dockermail01.mailtest.com/public_html>
Options FollowSymLinks
AllowOverride All
</Directory>
ProxyPassMatch "^/(.*\.php(/.*)?)$" "fcgi://app01:9000/var/www/vhosts/dockermail01.mailtest.com/public_html/$1"
CustomLog "logs/dockermail01.mailtest.com-access_log" combined
ErrorLog "logs/dockermail01.mailtest.com-error_log"
</VirtualHost>
→ dockermail01.mailtest.com でホストし、.php のリクエストはすべて app01サービス(PHP-FPMコンテナ)へローカルプロキシしています
各種ファイルの作成が完了したら、docker-compose.yml ファイルを配置しているディレクトリ上でイメージビルドとコンテナ立ち上げを実施します
イメージビルド
docker-compose build --no-cache
コンテナ起動
docker-compose up -d
起動確認
docker container ps -a
→ 下記のように表示されればOKです
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 32cf481c8bb6 docker-app01 "docker-php-entrypoi…" 20 hours ago Up 2 hours 9000/tcp docker-app01-1 7dc63cc887fa docker-web01 "httpd-foreground" 20 hours ago Up 2 hours 0.0.0.0:80->80/tcp, [::]:80->80/tcp docker-web01-1
4. ブラウザから表示確認
今回はローカル環境での確認となるので、/etc/hosts ファイル上でローカル名前解決をします
{サーバー1のIPアドレス} dockermail01.mailtest.com
それでは、http://dockermail01.mailtest.com へアクセスし、コンテンツが表示されていればOKです
ただ、この状態だとまだメールは送信できないので、以降の設定をします
5. msmtprc の説明
まずは今回の肝となる、msmtprc の説明をします
msmtprc はmsmtpの設定ファイルとなり、コンテナ内へ /etc/msmtprc として配置することで設定が有効なります
defaults auth off tls off logfile /var/log/msmtp.log account default host dockermail01.mailtest.com port 25 from user01@dockermail01.mailtest.com
→ 重要な部分のみ説明すると、host はsmtpサーバーを指定するので、今回の場合とメール送信サーバーであるサーバー1(/etc/hostsで設定した名前)を指定します
また、from はメール送信元アカウントを設定しているので、サーバー1内に作成している ユーザー名@dockermail01.mailtest.com を指定しています
6. Postfix の main.cf でDockerネットワークのIPを許可する
今回の構成だとメール送信経路は、サーバー1のPHP-FPMコンテナ → サーバー1上の Postfix となるので、 Postfix の mynetworks でDockerのネットワークを送信元として許可する必要があります
まずは、PHP-FPMコンテナのネットワークを確認します
docker inspect ls {コンテナID} | less
"Networks": {
"docker_default": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"docker-app01-1",
"app01"
],
"DriverOpts": null,
"GwPriority": 0,
"NetworkID": "d501262d819f04fc3b69a756da52cf2733ad8932ab6c710da16ec6caa60d4126",
"EndpointID": "2576460a524355db2032b70134ea017ab51621553097faf89a070c7d3e28a14c",
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.2",
"MacAddress": "e2:50:48:8c:84:de",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DNSNames": [
"docker-app01-1",
"app01",
"32cf481c8bb6"
]
}
}
},
→ 上記の場合だと、「”IPAddress”: “172.18.0.2”」であり、「”IPPrefixLen”: 16」 となっているので、許可するネットワーク帯は「172.18.0.0/16」になります
サーバー1の main.cf で先ほどのネットワーク帯を記載します
vi /etc/postfix/main.cf
mynetworks = 192.168.33.0/24, 127.0.0.0/8, 172.18.0.0/16
postfixを再起動します
systemctl restart postfix
7. ブラウザからメールを送信してみる
http://dockermail01.mailtest.com/mail_form.php へアクセスし、下記事項を埋めてメールを送信します
送り先:user02@mailclient01.mailtest.com
件名:test
メッセージ:test
→ 送り先にはサーバー2のpostfixで設定しているホスト名とユーザー名を指定します
8. サーバー2でメール受信確認
サーバー2の user02 でメールを受信していることを確認します
[user02@mailclient01 new]$ ls -la /home/user02/Maildir/new total 44 drwx------. 2 user02 user02 4096 Mar 14 07:05 . drwxr-xr-x. 5 user02 user02 39 Mar 7 09:10 .. -rw-------. 1 user02 user02 797 Mar 14 07:05 1773471912.V804I3044bdbM886760.mailclient01
[user02@mailclient01 new]$ cat 1773471912.V804I3044bdbM886760.mailclient01
Return-Path: <user01@dockermail01.mailtest.com>
X-Original-To: user02@mailclient01.mailtest.com
Delivered-To: user02@mailclient01.mailtest.com
Received: from dockermail01.mailtest.com (dockermail01.mailtest.com [192.168.33.95])
by mailclient01.mailtest.com (Postfix) with ESMTPS id BF7E83E5BE
for <user02@mailclient01.mailtest.com>; Sat, 14 Mar 2026 07:05:12 +0000 (UTC)
Received: from localhost (unknown [172.18.0.2])
by dockermail01.mailtest.com (Postfix) with ESMTP id B40592021DE5
for <user02@mailclient01.mailtest.com>; Sat, 14 Mar 2026 07:05:01 +0000 (UTC)
Date: Sat, 14 Mar 2026 07:05:01 +0000
To: user02@mailclient01.mailtest.com
Subject: test
From: user01@dockermail01.mailtest.com
MIME-Version: 1.0
Content-Type: text/plain; charset=ISO-2022-JP
Content-Transfer-Encoding: 7bit
test
→ きちんと受信できていればOKです
▼まとめ
いかかでしたでしょうか?
あまり本番環境でコンテナをホストすることはないかもしれませんが、メール送信の新しい知見として知れてよかったです。
ただ、まだまだメール送信技術は深いので引き続き勉強していきたいと思います。