AWS Fargateを使って、スケールするWordPress環境を作りたい。でも、正直よくわからない…。

そこで、いきなり完成形を目指すのではなく、「まずはコンテナでWordPressを動かす」という基本から始め、一つずつ仕組みを理解しながら、最終的に目標の構成にたどり着くまでの過程を記録してみました。

まず、ローカルで WordPress をコンテナで作ってみる

1. docker-compose.ymlを作成

# servicesセクション: アプリケーションを構成する各コンテナ(サービス)を定義
services:

  # 'db'という名前のサービス(MySQLデータベースコンテナ)を定義
  db:
    # 使用するDockerイメージを指定。ここではMySQLのバージョン8.0を使用。
    image: mysql:8.0
    # volumesセクション: データの永続化に関する設定。
    volumes:
      # 'db_data'という名前付きボリュームを、コンテナ内の'/var/lib/mysql'ディレクトリにマウント。
      # コンテナを起動するホストマシン上にデータを保持する領域が作られる。
      # これにより、コンテナを削除してもデータベースのデータが保持される。
      - db_data:/var/lib/mysql
    # restartポリシー: コンテナが停止した場合の再起動方針。'always'は常に再起動。
    restart: always
    # environmentセクション: コンテナ内で使用する環境変数を設定。
    environment:
      MYSQL_ROOT_PASSWORD: wordpress      # MySQLのrootユーザーのパスワードを設定。
      MYSQL_DATABASE: wordpress         # 'wordpress'という名前のデータベースを初期作成。
      MYSQL_USER: wordpress             # 'wordpress'という名前のユーザーを作成。
      MYSQL_PASSWORD: wordpress         # 上記ユーザーのパスワードを設定。

  # 'wordpress'という名前のサービス(WordPressアプリケーションコンテナ)を定義。
  wordpress:
    # depends_onセクション: サービスの起動順序を制御。
    # 'db'サービスが起動してから、この'wordpress'サービスが起動。
    depends_on:
      - db
    # 使用するDockerイメージを指定。ここでは最新版のWordPressを使用。
    image: wordpress:latest
    # portsセクション: ホストマシンとコンテナ間のポートマッピングを設定。
    # ホストマシンのポート8000番へのアクセスを、コンテナのポート80番に転送。
    # これにより、ブラウザで http://localhost:8000 を開くとWordPressにアクセスできる。
    ports:
      - "8000:80"
    # restartポリシー: コンテナが停止した場合、常に再起動。
    restart: always
    # environmentセクション: WordPressコンテナ用の環境変数を設定。
    environment:
      WORDPRESS_DB_HOST: db:3306        # 接続するデータベースのホスト名(サービス名'db')とポート番号を指定。
      WORDPRESS_DB_USER: wordpress      # データベースへの接続に使用するユーザー名を指定。
      WORDPRESS_DB_PASSWORD: wordpress  # データベースへの接続に使用するパスワードを指定。
      # WORDPRESS_DB_NAMEは、MYSQL_DATABASEで指定した'wordpress'が自動的に使われる。

# volumesセクション: このdocker-composeファイル全体で使用する名前付きボリュームを定義。
volumes:
  # 'db_data'という名前のボリュームを定義。実体はDockerが管理。
  db_data:

2. コンテナを起動
DockerDesktopを起動し、docker-compose up -d でコンテナを起動します。

y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ docker-compose up -d
--省略--                                                      22.3s 
[+] Running 4/4
 ✔ Network y-ishikawa-docker-wordpress_default        Created                                0.1s 
 ✔ Volume "y-ishikawa-docker-wordpress_db_data"       Created                                0.0s 
 ✔ Container y-ishikawa-docker-wordpress-db-1         Started                                0.7s 
 ✔ Container y-ishikawa-docker-wordpress-wordpress-1  Started                                0.6s 
y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ 

3. 確認
ブラウザから `http://localhost:8000` にアクセスします。

できた

webサーバー(Apache)とPHPの処理を分けてみる

静的ファイル(画像など)はwebコンテナで返し、PHPのリクエストはcmsコンテナへ転送するようにしようと思いました。
1. docker-compose.yml を修正

services:
    # DBサービスは変更なし
    db:
      image: mysql:8.0
      command: --default-authentication-plugin=mysql_native_password # MySQLの認証プラグインを指定(これがないとうまくいかない)
      volumes:
        - db_data:/var/lib/mysql
      restart: always
      environment:
        MYSQL_ROOT_PASSWORD: wordpress
        MYSQL_DATABASE: wordpress
        MYSQL_USER: wordpress
        MYSQL_PASSWORD: wordpress

    # (旧wordpressサービス)PHPの実行に特化させる
    cms:
      image: wordpress:php8.2-fpm-alpine # PHP-FPM版のイメージに変更し、webサーバー機能がないPHP処理専用のコンテナにする
      depends_on:
        - db
      restart: always
      volumes:
        - wordpress_files:/var/www/html # WordPressのファイルを格納するボリュームを指定
      environment:
        WORDPRESS_DB_HOST: db:3306
        WORDPRESS_DB_USER: wordpress
        WORDPRESS_DB_PASSWORD: wordpress
        WORDPRESS_DB_NAME: wordpress # データベース名を指定するように変更

    # 【追加】apacheのwebサーバーサービス
    web:
      image: httpd:2.4-alpine # Apacheの公式イメージ
      depends_on:
        - cms # cmsコンテナが起動してから起動
      restart: always
      ports:
      - "8000:80" # 外部との接続ポートはこっちに移動
      volumes:
      - wordpress_files:/var/www/html # cmsと共有するボリュームを指定することで、Apacheは画像やCSSなどの静的ファイルを直接配信でき、PHPファイルはcmsコンテナに処理を渡すパスを認識できる。
      - ./apache/httpd.conf:/usr/local/apache2/conf.d/httpd.conf # ローカルのApache設定ファイルを読み込ませる

volumes:
  db_data:
  wordpress_files: # WordPressのファイルを格納する新しいボリューム

2. apacheの設定ファイルを作成

.
├── apache/
│   └── httpd.conf  # <-- 新しく作成するApacheの設定ファイル
└── docker-compose.yml

httpd.conf ↓

y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ mkdir apache
y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ cd apache/
y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress/apache$ cat httpd.conf
# サーバーの基本的な動作に必要なモジュール
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule dir_module modules/mod_dir.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule rewrite_module modules/mod_rewrite.so

# プロキシに必要なモジュール
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so

# UserとGroupはhttpdイメージのデフォルトに合わせておく
User daemon
Group daemon

# サーバーの基本設定
ServerRoot "/usr/local/apache2"
Listen 80
ServerName localhost

# ドキュメントルート(Webコンテンツの場所)
DocumentRoot /var/www/html

# ディレクトリの設定

    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted


# index.phpをデフォルトページに
DirectoryIndex index.php

# .phpファイルへのリクエストをcmsコンテナに転送する
ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://cms:9000/var/www/html/$1

# ログのフォーマットを定義
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

# エラーログとアクセスログの設定
ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 combined
y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress/apache$ 

3. コンテナを起動

y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ docker-compose up -d
[+] Running 6/6
 ✔ Network y-ishikawa-docker-wordpress_default           Created                             0.1s 
 ✔ Volume "y-ishikawa-docker-wordpress_wordpress_files"  Created                             0.0s 
 ✔ Volume "y-ishikawa-docker-wordpress_db_data"          Created                             0.0s 
 ✔ Container y-ishikawa-docker-wordpress-db-1            Started                             0.5s 
 ✔ Container y-ishikawa-docker-wordpress-cms-1           Started                             0.6s 
 ✔ Container y-ishikawa-docker-wordpress-web-1           Started                             0.8s 
y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ 


できた

4. 動作確認
4-1. 静的ファイルがwebコンテナで処理されることを確認
ブラウザでWordPressのサイトのトップページを何度かリロードし、webコンテナのログが出力されることを確認。

y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ docker-compose logs -f web
web-1  | 192.168.65.1 - - [19/Jul/2025:13:28:17 +0000] "GET /wp-admin/admin-ajax.php?action=wp-compression-test&test=1&_ajax_nonce=c07f70353f&1752931697561 HTTP/1.1" 200 1114 "http://localhost:8000/wp-admin/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) 
web-1  | 192.168.65.1 - - [19/Jul/2025:13:28:27 +0000] "POST /wp-admin/
web-1  | 192.168.65.1 - - [19/Jul/2025:13:28:35 +0000] "GET / HTTP/1.1" 200 57198 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"

4-2. PHPがcmsコンテナで処理されることを確認
ブラウザでWordpressの管理画面にアクセスし、cmsコンテナのログが出力されることを確認。

y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ docker-compose logs -f cms
cms-1  | 172.19.0.4 -  19/Jul/2025:13:27:23 +0000 "GET /index.php" 500
cms-1  | 172.19.0.4 -  19/Jul/2025:13:27:34 +0000 "GET /index.php" 302
cms-1  | 172.19.0.4 -  19/Jul/2025:13:27:34 +0000 "GET /wp-admin/install.php" 200
cms-1  | 172.19.0.4 -  19/Jul/2025:13:27:42 +0000 "POST /wp-admin/install.php" 200
cms-1  | 172.19.0.4 -  19/Jul/2025:13:28:01 +0000 "POST /wp-admin/install.php" 200

できました。dbコンテナに command: --default-authentication-plugin=mysql_native_password を追加しないと認証のタイプがどうのでうまくいかないので忘れないず設定しましょう。

AWS Fargate で起動してみる

web (Apache) コンテナは自作の httpd.conf を使っているため、この設定ファイルを含んだカスタムイメージを Dockerfile で作成し、ECRにプッシュする必要があるようです。

1. webコンテナ用の Dockerfile を作成する

.
├── apache/
│   └── httpd.conf
├── docker-compose.yml
└── Dockerfile  # <-- これを新規作成

Dockerfile ↓

# ベースイメージとして公式のhttpdイメージを指定
FROM httpd:2.4-alpine

# ローカルのhttpd.confをコンテナ内の正しい場所にコピーする
COPY ./apache/httpd.conf /usr/local/apache2/conf/httpd.conf

2. ECRにリポジトリを作成
Amazon ECR > プライベートレジストリ > リポジトリ >プライベートリポジトリを作成
リポジトリ名だけ決めて、他はデフォルトで「作成」をクリックします。

3. httpd.conf の修正
docker-composeでは各サービスがコンテナ名(例: cms)で通信できましたが、Fargateの同一タスク内のコンテナは localhost で通信します。
そのため、apache/httpd.conf のPHPへの転送設定を以下のように変更します。

# 変更前
# ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://cms:9000/var/www/html/$1

# 変更後: 'cms:9000' を 'localhost:9000' に変更
ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://localhost:9000/var/www/html/$1

4. イメージをビルドしてECRにプッシュする
作成したリポジトリを選択 > プッシュコマンドを表示 >
表示されたコマンドをターミナルから順に実行します(AWSアカウントの認証情報は取得しておいてください)

y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded
y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ docker build -t y-ishikawa/web .
[+] Building 0.1s (7/7) FINISHED                                             docker:desktop-linux
--省略--
y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ docker tag y-ishikawa/web:latest ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/y-ishikawa/web:latest
y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ docker push ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/y-ishikawa/web:latest
The push refers to repository [ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/y-ishikawa/web]
--省略--
y-ishikawa@y-ishikawa1:~/y-ishikawa-docker-wordpress$ 

5. ECSタスク定義の作成
タスク定義は、Fargateで起動するコンテナ群の設計図で、docker-compose.yml にあたるものです。
ECS > タスク定義 > 新しいタスク定義の作成
以下の内容で作成をクリック

【基本設定】
起動タイプ: AWS Fargate
OS/アーキテクチャ: Linux/ARM64
タスクサイズ: 
    CPU: 2vCPU
    メモリ: 5GB
その他: デフォルト

【コンテナ定義】
① web コンテナ
名前: ewb
イメージ: 先ほどECRにプッシュしたイメージのURI
必須コンテ: はい
ポートマッピング: 
    コンテナポート: 80
    プロトコル: TCP
    ポート名: 空欄か任意で入力
    アプリケーションプロトコル: HTTP
その他: デフォルト

② db コンテナ
名前: db
イメージ: mysql:8.0(イメージURIの欄に入力して大丈夫、URIがECRの形式でない場合、自動的にDocker Hubからイメージを取得)
必須コンテナ: はい
環境変数:
    MYSQL_ROOT_PASSWORD: wordpress
    MYSQL_DATABASE: wordpress
    MYSQL_USER: wordpress
    MYSQL_PASSWORD: wordpress
HealthCheck:
    コマンド: CMD-SHELL,mysql -h localhost -u root -p"$MYSQL_ROOT_PASSWORD" -e "USE wordpress" || exit 1
    間隔: 10
    タイムアウト: 5
    再試行: 3
Docker設定 > コマンド: --default-authentication-plugin=mysql_native_password
その他: デフォルト

③ cms コンテナ
名前: cms
イメージ: wordpress:php8.2-fpm-alpine
必須コンテナ: はい
環境変数: 
    WORDPRESS_DB_HOST: 127.0.0.1
    WORDPRESS_DB_USER: wordpress
    WORDPRESS_DB_PASSWORD: wordpress
    WORDPRESS_DB_NAME: wordpress
スタートアップの依存関係の順序:
    コンテナ名: db
    条件: Healthy
その他: デフォルト

【オプション設定】
ボリューム:
    ボリューム名: 任意
コンテナマウントポイント:
    マウントポイント①:
        コンテナ: web
        ソースボリューム: 設定したボリューム名
        コンテナパス: /var/www/html
    マウントポイント②:
        コンテナ: cms
        ソースボリューム: 設定したボリューム名
        コンテナパス: /var/www/html

mac を使っている場合は、OS/アーキテクチャでLinux/ARM64を選ばないとうまくいかないです。
あとは、メモリは4GBか5GBくらいないといけない、cmsコンテナの環境変数WORDPRESS_DB_HOSTは、localhostではなく127.0.0.1じゃないとうまくいかないなど、はまりポイントが多かったです。

6. ECSサービスを作成してタスクを起動する
作成したタスク定義を実行し、外部からアクセスできるように「サービス」を作成します。
6-1. ECSクラスターの作成

クラスター名: 任意
インフラストラクチャ: AWS Fargate

6-2. サービスの作成
作成したクラスター > サービス > 作成
以下の内容で、作成をクリック

タスク定義ファミリー: 先ほど作成したタスク定義を選択
サービス名: 任意
起動タイプ: FARGATE
ネットワーキング:
    VPC: 任意のもの
    サブネット: 任意のもの
    セキュリティグループ:
        タイプ: HTTP
        ソース: Anywhere
    パブリックIP: オン

作成をクリックし、タスクのステータスが「実行中」になっていることを確認します。

7. 動作確認
起動されたタスクのIDをクリックし、IPを確認して、ブラウザからアクセスすると、おなじみの画面が表示されました。

本番構成にアップグレード

最後に、以下のスケーラブルで堅牢な構成にアップグレードします。

ALB → Fargateに、Fargateをプライベートサブネットに、データベースをRDS、コンテンツ共有をEFS、ログをFireLens → S3にします。
1. ネットワーク設定を整備
1-1. NATゲートウェイの作成
パブリックサブネットの一つにNATゲートウェイを作成
1-2. ルートを設定
プライベートサブネットのルートテーブルを編集し、インターネットへの通信(0.0.0.0/0)がNATゲートウェイに向かうように設定します。
これにより、プライベートサブネット内のFargateタスクがコンテナイメージをプルしたり、WordPressが外部にアップデートを確認しに行けるようになります。

2. RDSを作成
以下の設定で作成します。

エンジンのタイプ: MySQL
エンジンバージョン: 8.0.41
VPC: Fargateと同じもの
サブネットグループ: 任意のプライベートサブネット
セキュリティグループ:
    タイプ: MySQL/Aurora
    ポート: 3306
    ソース: ecsのセキュリティグループ
追加設定:
    最初のデータベース名: wordpress
その他: 任意で設定

3. EFSを作成
ファイルシステムの作成からカスタマイズを選択して、以下の設定で作成します。

VPC: Fargateと同じもの
ファイルシステムのタイプ: リージョン
マウントターゲット: 
    サブネット: プライベートサブネットを選択
    セキュリティグループ:
        タイプ: nfs
        ポート: 2049
        ソース: ecsのセキュリティグループ
その他: 任意で設定

4. ログ保管用S3バケットを作成

5. Fargate用のタスクロールを作成
ECSタスクのコンテナがAWSサービスへのAPIリクエストを行うことを許可するロールを作成します。
以下設定で作成します。

信頼されたエンティティ: AWSのサービス
ユースケース: Elastic Container Service Task
許可ポリシー: 
    AmazonS3FullAccess(FireLensがS3にログを書き込むために必要)
    AmazonElasticFileSystemClientReadWriteAccess(FargateタスクがEFSをマウントするために必要)

6. タスク定義の更新
既存のタスク定義に以下の変更を加え、新しいリビジョンを作成します。

タスクロール: 先ほど作成したタスクロールを選択
dbコンテナ: 削除する
webコンテナ:
    ログ記録: 「AWS FireLens 経由でS3にログをエクスポートする」
        bucket: 作成したS3バケット名
        その他: デフォルト
cmsコンテナ:
    環境変数:
        WORDPRESS_DB_HOST: RDSのエンドポイント
        WORDPRESS_DB_USER: RDSで設定したマスターユーザー名
        WORDPRESS_DB_PASSWORD: RDSで設定したマスターパスワード
        WORDPRESS_DB_NAME: RDSで作成したデータベース名
    スタートアップの依存関係の順序: 削除する
    ログ記録: 「AWS FireLens 経由でS3にログをエクスポートする」
        bucket: 作成したS3バケット名
        その他: デフォルト
ボリューム:
    既存のボリューム: 削除
    ボリュームの追加:
        ボリューム名: 任意
        ボリュームタイプ: EFS
        ファイルシステムID: 作成したEFSを選択
        ルートディレクトリ: /
    マウントポイントの追加:
        マウントポイント①:
            コンテナ: web
            ソースボリューム: 設定したボリューム名
            コンテナパス: /var/www/html
        マウントポイント②:
            コンテナ: cms
            ソースボリューム: 設定したボリューム名
            コンテナパス: /var/www/html

ログ記録でFireLensを選択するとログルーティングコンテナ (FireLens) というコンテナが自動で追加されました。
本番環境では、パスワードなどはSecrets Managerで管理してください。
7. ALBを作成
7-1. ターゲットグループを作成

ターゲットタイプ: IPアドレス
プロトコル: HTTP
ポート: 80
VPC: Fargateと同じVPC
ヘルスチェック:
    成功コード: 200-399(wordpressインストール画面にリダイレクトされるのを成功として扱うようにするため)

ALBをECS用で利用する場合、タスクがスケーリングしても、ECSサービスがALBと連携し、タスクのIPアドレスを自動的に登録・解除するみたいです。
ターゲットグループ作成時は何も登録しなくて大丈夫です。
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-load-balancing.html
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/alb.html

7-2. ALB作成

スキーム: インターネット向け
VPC: Fargateと同じVPC
サブネット: 任意のパブリックサブネット
セキュリティグループ:
    タイプ: HTTP
    ポート: 80
    ソース: Anywhere
    リスナー:
        プロトコル: HTTP
        ポート: 80
        転送先: 作成したターゲットグループ

8. ECSサービスの更新
8-1. セキュリティグループの修正
ecsにつけていたセキュリティグループに以下のインバウンドルールを追加します。

①:
    タイプ: HTTP
    ポート: 80
    ソース: ALBのセキュリティグループ
②:
    タイプ: MySQL/Aurora
    ポート: 3306
    ソース: RDSのセキュリティグループ
③:
    タイプ: NFS
    ポート: 2049
    ソース: EFSのセキュリティグループ

8-2. ECSサービスの更新

タスク定義: 更新したタスク定義の最新のリビジョンを選択
起動タイプ: FARGATE
VPC: これまでと同じVPC
サブネット: EFSのマウントターゲットで指定したプライベートサブネットを選択
セキュリティグループ: 前回と同じセキュリティグループ
ロードバランシング:
    種類: ALB
    コンテナ: web 80:80
    ロードバランサー: 作成したALBを選択
    リスナー: 作成したリスナーを選択
    ターゲットグループ: 作成したターゲットグループ


9. 動作確認
9-1. ALBのDNS名にブラウザからアクセスし、WordPressの初期設定画面が表示されることを確認

9-2. WordPressのインストールを進め、記事の投稿、画像のアップロードが問題なくできることを確認
記事の投稿を確認

画像のアップロードを確認

9-3. 記事投稿時にRDSの接続数メトリクスが増えていることを確認

9-4. EFSでファイルが永続化できていることを確認
ECSのタスクを停止


タスクが新たに起動されることを確認

ブラウザを更新し、画像が表示されることを確認

9-5. S3バケットにログファイルが出力されていることを確認

これで全てできました!!

まとめ

はまりポイントは多いですが、一つひとつ進めることで理解することができました。
CI/CDやスケーリング設定、サービス間の連携なども今後理解していきたいです。
参考になったら幸いです。

参考記事

クィックスタート: Compose と WordPress
https://docs.docker.jp/compose/wordpress.html
ロードバランサーを使用して Amazon ECS サービストラフィックを分散する
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-load-balancing.html
Amazon ECS 用の Application Load Balancer を使用する
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/alb.html
Amazon EFS を利用した Amazon ECS on AWS Fargate での WordPress の実行
https://aws.amazon.com/jp/blogs/news/running-wordpress-amazon-ecs-fargate-ecs/
スタートアップのためのコンテナ入門 – AWS Fargate 編
https://aws.amazon.com/jp/blogs/startup/techblog-container-fargate-1/