2026年5月23日
2026年5月23日
WordPressのサーバー構成をTerraformでコード化して管理する方法
WordPressを動かすサーバー、データベース、ファイアウォールの設定をTerraformのコードに書いておくと、構成の変更を差分として確認してから適用でき、同じ環境をステージング用にもう1つ作ることも簡単になります。この記事では、WordPress向けの構成をTerraformで書くときに「何をTerraformに任せ、何を任せないか」を決めたうえで、AWSを例にしたコードの書き方、state(状態ファイル)と秘密情報の扱い、DBを誤って消さないための設定までを説明します。
Terraformのインストールや基本文法は別記事で扱っているので、ここではWordPressを運用する視点に絞ります。
Terraformに任せる範囲を決める
Terraformは「インフラの形」を宣言的に管理する道具です。WordPressの運用に当てはめると、次のように分けるとうまくいきます。
- Terraformで管理する:仮想サーバー、ネットワーク、ファイアウォール(セキュリティグループ)、マネージドDB、ストレージのバケット、DNSレコード、ロードバランサー
- Terraform以外で管理する:OS上のパッケージ設定(cloud-init、Ansibleなど)、WordPress本体・テーマ・プラグインのデプロイ(Git+デプロイの仕組み、WP-CLI)、投稿やメディアなどのコンテンツ(WordPressのバックアップ)
WordPressのプラグインや記事までTerraformで管理しようとすると、管理画面での変更とコードが食い違い続けることになります。Terraformが扱うのは「サーバーを何台、どの設定で用意するか」までにしておくと、責任の境界がはっきりします。
なお、TerraformはHashiCorpが提供するツールで、2023年にライセンスがBSLに変更されました。オープンソースのフォークとしてOpenTofuがあり、基本的な書き方は共通です。組織の方針によってはライセンスの確認が必要です。
ディレクトリ構成
wp-infra/
├── versions.tf # Terraform とプロバイダーのバージョン、state の保存先
├── variables.tf # 入力変数
├── main.tf # サーバー・DB・セキュリティグループ
├── outputs.tf # 作成後に表示したい値
├── cloud-init.yaml # サーバー起動時の初期設定
└── envs/
├── staging.tfvars
└── production.tfvars
環境ごとの違い(インスタンスの大きさ、ドメイン名など)は .tfvars に分け、コード本体は共通にします。ステージングと本番を同じコードから作れることが、Terraformを使う大きな利点です。
バージョンとstateの保存先を固定する
# versions.tf
terraform {
required_version = ">= 1.11"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
backend "s3" {
bucket = "your-tfstate-bucket"
key = "wordpress/production.tfstate"
region = "ap-northeast-1"
encrypt = true
use_lockfile = true
}
}
provider "aws" {
region = var.region
}
state(terraform.tfstate)は、Terraformが「実際に何を作ったか」を記録するファイルです。手元のPCに置いたままだと、別の人が同時に実行したときに壊れたり、PCの故障で失われたりします。チームで使うならリモートのバックエンドに置き、同時実行を防ぐロックを有効にします。S3バックエンドの use_lockfile はS3だけでロックを取る比較的新しい方式で、以前はDynamoDBのテーブルを使っていました。どちらが使えるかはTerraformのバージョンによるため、使うバージョンのドキュメントで確認してください(2026年10月現在)。プロバイダーのメジャーバージョンも、更新で書き方が変わることがあるので ~> で固定しておきます。
サーバー・DB・ファイアウォールを書く
AWSでWebサーバー1台とマネージドDB(RDS for MySQL)を作る例です。ネットワークは説明を短くするため既定のVPCを使っています。本番ではVPCとサブネットも定義し、DBはプライベートサブネットに置いてください。
# variables.tf
variable "region" {
type = string
default = "ap-northeast-1"
}
variable "env" {
type = string
}
variable "instance_type" {
type = string
default = "t3.small"
}
# main.tf
data "aws_vpc" "default" {
default = true
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
}
resource "aws_security_group" "web" {
name = "wp-web-${var.env}"
vpc_id = data.aws_vpc.default.id
}
resource "aws_vpc_security_group_ingress_rule" "web_https" {
security_group_id = aws_security_group.web.id
cidr_ipv4 = "0.0.0.0/0"
from_port = 443
to_port = 443
ip_protocol = "tcp"
}
resource "aws_vpc_security_group_egress_rule" "web_all" {
security_group_id = aws_security_group.web.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "-1"
}
resource "aws_security_group" "db" {
name = "wp-db-${var.env}"
vpc_id = data.aws_vpc.default.id
}
# DB へは Web サーバーのセキュリティグループからだけ接続を許可する
resource "aws_vpc_security_group_ingress_rule" "db_from_web" {
security_group_id = aws_security_group.db.id
referenced_security_group_id = aws_security_group.web.id
from_port = 3306
to_port = 3306
ip_protocol = "tcp"
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.web.id]
user_data = file("${path.module}/cloud-init.yaml")
tags = {
Name = "wp-web-${var.env}"
}
}
resource "aws_db_instance" "wordpress" {
identifier = "wp-db-${var.env}"
engine = "mysql"
engine_version = "8.4"
instance_class = "db.t4g.micro"
allocated_storage = 20
storage_encrypted = true
db_name = "wordpress"
username = "wpadmin"
manage_master_user_password = true
vpc_security_group_ids = [aws_security_group.db.id]
backup_retention_period = 7
deletion_protection = true
skip_final_snapshot = false
final_snapshot_identifier = "wp-db-${var.env}-final"
lifecycle {
prevent_destroy = true
}
}
コードのポイントを説明します。
- AMIは名前で検索する:AMIのIDはリージョンや時期で変わるため、
data "aws_ami"で最新のUbuntuを探しています。ただしmost_recent = trueのままだと、新しいAMIが出るたびにplanでサーバーの作り直しが提案されます。本番では検索結果を固定するか、lifecycleのignore_changes = [ami]を検討してください - SSHの22番は開けていない:サーバーへの接続にはAWS Systems Managerのセッションマネージャーを使う、または自分のIPアドレスだけに限定したルールを追加する、のどちらかにします。
0.0.0.0/0からの22番は避けてください - DBのパスワードをコードに書かない:
manage_master_user_password = trueにすると、パスワードはAWS Secrets Managerで生成・管理され、コードにもstateにも平文で入りません。WordPressのwp-config.phpには、サーバーの初期設定の段階でSecrets Managerから取得して書き込みます - DBを消させない設定を重ねる:
deletion_protectionはAWS側で削除を拒否し、prevent_destroyはTerraformが削除を伴う計画を立てた時点でエラーにします。skip_final_snapshot = falseは、それでも削除するときに最後のスナップショットを残す設定です
MySQLのバージョン(ここでは8.4)や、インスタンスクラスの選択肢は、RDSのサポート状況によって変わります。WordPressが推奨するMySQL/MariaDBのバージョンとあわせて、適用前に確認してください。
秘密情報とstateの注意点
variable に sensitive = true を付けると、plan の出力で値が伏せられます。ただし、stateファイルには値がそのまま記録されることがあります。stateを置くバケットは暗号化し、アクセスできる人を絞ってください。新しいTerraformには、stateに残さない値を扱うための仕組み(ephemeralな値やwrite-only属性)も追加されていますが、対応するリソースはプロバイダーのバージョンによって異なります。
Gitには次のファイルをコミットしないよう .gitignore に入れます。
.terraform/
*.tfstate
*.tfstate.*
*.tfplan
crash.log
一方、.terraform.lock.hcl(プロバイダーのバージョンを固定するファイル)はコミットします。チーム全員が同じプロバイダーで実行できるようにするためです。.tfvars に秘密情報を入れる運用なら、それも除外対象です。
変更の流れ:plan を読んでから apply する
terraform init
terraform fmt -check
terraform validate
terraform plan -var-file=envs/staging.tfvars -out=staging.tfplan
terraform apply staging.tfplan
plan の出力で最も注意して読むのは、-/+(作り直し)と -(削除)の行です。サーバーのAMIやDBの一部の設定は、変更すると「作り直し」になり、サーバー上のデータが失われます。must be replaced と表示されたリソースがあれば、本当に作り直してよいかを確認してから進めてください。plan の結果をファイルに保存して apply に渡すと、確認した内容と違う変更が適用されることを防げます。
いきなり本番に適用せず、ステージング用の .tfvars で一度作り、WordPressが動くことを確かめてから本番に適用する流れにしておくと安全です。
既存のサーバーをTerraform管理に移す
すでに手作業で作ったサーバーやDBがある場合は、作り直さずにTerraformの管理下に取り込めます。Terraform 1.5以降では、import ブロックで取り込み対象を指定できます。
import {
to = aws_db_instance.wordpress
id = "wp-db-production"
}
terraform plan を実行すると、取り込み後に差分が出るかを確認できます。コードと実際の設定が一致するまで(差分がなくなるまで)コードを調整してから apply します。差分を残したまま適用すると、手作業で設定していた値がコードの値で上書きされるため、取り込みの前にはDBのスナップショットを取っておいてください。
まとめ
- Terraformはサーバー・ネットワーク・DBなど「インフラの形」に使い、WordPressのコードやコンテンツは別の仕組みで管理する
- stateはリモートに置いてロックを有効にし、プロバイダーのバージョンを固定する
- DBのパスワードはコードに書かず、
manage_master_user_passwordなどで外部管理する deletion_protection、prevent_destroy、最終スナップショットでDBの誤削除を防ぐplanの作り直し・削除の行を読み、保存したplanをapplyする。ステージングで先に試す- 既存環境は
importブロックで取り込み、差分がなくなってから適用する