G-gen の高井(Peacock)です。Cloud Run のサイドカー機能と MailHog を利用し、アプリケーションからのメールを捕捉するダミーの SMTP サーバー環境を構築する方法を紹介します。

はじめに
やりたいこと
Cloud Run services を使い、メール送信機能があるアプリケーションを開発しているケースを考えます。本番環境のアプリケーションでは SMTP サーバーを利用してメールを送信していますが、開発環境のアプリケーションではメールが外部に送信されないようにしたい場合があります。
当記事では、上記のようなケースにおいて、アプリケーションのコードは極力変更せずに、開発環境のみメールを MailHog で構築したダミー SMTP サーバーで受け取るようにします。
MailHog は、MIT ライセンスのもと配布されているオープンソースソフトウェアです。
実現方法
当記事では、前述の要件を Cloud Run のサイドカー(Sidecar)機能を利用して実現します。
アプリケーションコンテナのデプロイ時に、MailHog のコンテナをサイドカーとして同時にデプロイします。これにより、2つのコンテナは同一の Cloud Run サービスインスタンス内で起動し、localhost を通じて通信できます。
アプリケーション側では、接続先 SMTP サーバーのホスト名を localhost に、ポートを MailHog が待機するポート(デフォルトで 1025)に設定するだけで、メール送信の向き先を MailHog に変更できます。
サイドカー機能とは
サイドカーとは、1つの Cloud Run サービスに、複数のコンテナをデプロイできる機能です。
メインのリクエストを処理するコンテナ(Ingress コンテナ)と、それを補助するサイドカーコンテナを組み合わせて使用します。サイドカーのユースケースとして、ログ転送エージェントや、サービスメッシュのプロキシ、そして今回のような補助的なサービスが挙げられます。
サイドカーとしてデプロイされたコンテナは、Ingress コンテナと同じネットワーク名前空間を共有するため、localhost を使用して相互に通信できます。ただし、外部からの HTTP リクエストは Ingress コンテナのみが受信し、サイドカーコンテナのポートが直接外部に公開されることはありません。
構成
外部からのリクエストは、ポート 8080 で待機する FastAPI アプリケーションコンテナ(Ingress コンテナ)が受け取ります。
アプリケーションがメールを送信する際は、環境変数で指定された localhost:1025 に接続します。そこでは MailHog コンテナが SMTP サーバーとして待機しており、送信されたメールを捕捉します。
ソースコード
まず、メール送信機能を持つ FastAPI アプリケーションを用意します。
重要な点は、SMTP サーバーの接続情報を環境変数(SMTP_HOST、SMTP_PORT など)から読み込むように実装することです。これにより、コンテナイメージを再ビルドすることなく、デプロイ時の設定だけで接続先を切り替えられます。
Dockerfile
FROM python:3.13-bookworm COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ WORKDIR /app ADD . /app RUN uv sync --locked EXPOSE 8080 CMD ["/app/.venv/bin/fastapi", "run", "main.py", "--host", "0.0.0.0", "--port", "8080"]
main.py
from email.message import EmailMessage import smtplib import os from fastapi import FastAPI, HTTPException, Request, Response from pydantic import BaseModel, EmailStr app = FastAPI() # 環境変数からSMTPサーバーの接続情報を取得 SMTP_HOST = os.environ.get("SMTP_HOST") SMTP_PORT = int(os.environ.get("SMTP_PORT", 587)) SMTP_USER = os.environ.get("SMTP_USER") SMTP_PASS = os.environ.get("SMTP_PASS") class EmailModel(BaseModel): from_: EmailStr to: EmailStr subject: str body: str @app.get("/") def root(): return {"message": "ok"} @app.post("/sendmail") def sendmail(email: EmailModel): if SMTP_HOST is None: raise HTTPException( status_code=500, detail="SMTP_HOST environment variable is not set" ) # MailHogは認証不要なため、USER/PASSの有無で処理を分岐 if SMTP_USER and SMTP_PASS: server = smtplib.SMTP_SSL(SMTP_HOST, port=SMTP_PORT) server.set_debuglevel(1) server.login(user=SMTP_USER, password=SMTP_PASS) else: server = smtplib.SMTP(SMTP_HOST, port=SMTP_PORT) server.set_debuglevel(1) msg = EmailMessage() msg["Subject"] = email.subject msg["From"] = email.from_ msg["To"] = email.to msg.set_content(email.body) server.send_message(msg) server.close() return {"ok": True}
pyproject.toml
[project] name = "etude-mailhog" version = "0.1.0" description = "" readme = "README.md" requires-python = ">=3.13" dependencies = [ "fastapi[standard]>=0.115.12", ]
デプロイ
gcloud コマンドで、アプリケーションと MailHog を Cloud Run にデプロイします。
まず、アプリケーションのコンテナイメージを Artifact Registry などにプッシュします。
# Cloud BuildでコンテナイメージをビルドしてArtifact Registryにプッシュ # (事前に gcloud artifacts repositories create ... でリポジトリ作成が必要) gcloud builds submit --tag asia-northeast1-docker.pkg.dev/$(gcloud config get-value project)/smtp-mocker/app:1.0
次に、Cloud Run にサービスをデプロイします。--container フラグを2つ指定することで、サイドカー構成を定義します。
gcloud run deploy mail-service \ --region asia-northeast1 \ --allow-unauthenticated \ --container "app" \ --image "asia-northeast1-docker.pkg.dev/$(gcloud config get-value project)/smtp-mocker/app:1.0" \ --port 8080 \ --set-env-vars "SMTP_HOST=localhost,SMTP_PORT=1025" \ --container "mailhog" \ --image "mailhog/mailhog"
このコマンドの主要なフラグは以下の通りです。
| フラグ | 説明 |
|---|---|
--container "app" |
Ingress コンテナとなるアプリケーションを定義します。このフラグに続く --port 8080 で待ち受けポートを指定し、--set-env-vars で SMTP の接続先を localhost:1025 に設定します。 |
--container "mailhog" |
サイドカーとなる MailHog コンテナを定義します。Docker Hub の公式イメージ mailhog/mailhog を指定します。 |
動作確認
デプロイが完了したら、curl コマンドでアプリケーションの /sendmail エンドポイントを呼び出し、テストメールを送信します。
# Cloud RunサービスのエンドポイントURLを取得 SERVICE_URL=$(gcloud run services describe mail-service --region asia-northeast1 --format 'value(status.url)') # テストメールを送信 curl -X POST "${SERVICE_URL}/sendmail" \ -H "Content-Type: application/json" \ -d '{ "from_": "from@example.com", "to": "to@example.com", "subject": "Test from Cloud Run", "body": "This is a test mail with a sidecar." }'
このように Cloud Run のサイドカー機能と環境変数を利用することで、アプリケーションコードを変更することなく、環境に応じた柔軟なシステム構成を実現できます。
Peacock (高井 陽一)(記事一覧)
クラウドソリューション部 カスタマーサポート課
2022年12月より入社。普段は Google Cloud の技術サポートや Terraform などによる IaC の推進を行なっている。また、プライベートでは PyCon JP 2022, 2023(APAC) にて副座長など、カンファレンスのスタッフとしても活動している。趣味はカメラ・スキー・クラシック音楽など。
Follow @peacock0803sz