หมวดหมู่: Programming

คู่มือการสร้าง WordPress Local Development Stack ด้วย Docker (Nginx + PHP-FPM + MariaDB)คู่มือการสร้าง WordPress Local Development Stack ด้วย Docker (Nginx + PHP-FPM + MariaDB)

การสร้างสภาพแวดล้อมจำลอง (Local Test Stack) สำหรับ WordPress ด้วย Docker ช่วยให้การพัฒนาเว็บมีความยืดหยุ่น ควบคุมเวอร์ชันได้ง่าย และแยกการทำงานออกจากเครื่องหลักได้อย่างเด็ดขาด บทความนี้จะพาสร้าง Stack แบบแยกส่วน (Decoupled Architecture) ระหว่าง Nginx, PHP-FPM และ MariaDB พร้อมระบบสคริปต์อัตโนมัติ


โครงสร้างโปรเจกต์และไฟล์จำเป็น

สร้างโฟลเดอร์โปรเจกต์ของคุณ (เช่น wp-dev-stack) โดยจะมีโครงสร้างไฟล์หลักดังนี้

<pre class="wp-block-syntaxhighlighter-code">/Users/common/Docker/
├── .env                      <-- ไฟล์ตัวแปรสภาพแวดล้อมส่วนกลาง
└── app_wordpress_71_dev/
    ├── build.sh              <-- สคริปต์ดาวน์โหลด Core, ซิงก์ .env ลง config และเตรียมไฟล์ระบบ
    ├── run.sh                <-- สคริปต์สตาร์ต Service คอนเทนเนอร์ (Web + App) และ Import DB
    ├── docker-compose.yml    <-- นิยามเซอร์วิส Nginx (web) และ PHP-FPM (app)
    ├── init.sql              <-- SQL Template สำหรับสร้าง Table Schema และ Admin ผู้ใช้
    ├── nginx/
    │   └── default.conf      <-- คอนฟิก Reverse Proxy และ FastCGI ชี้ไปที่ PHP-FPM
    └── src/                  <-- โค้ดโปรเจกต์ WordPress ทั้งหมด (Bind Mount)
        ├── wp-config.php     <-- ไฟล์คอนฟิกหลักที่รับค่าจาก .env และ Salt Keys อัตโนมัติ
        ├── wp-content/       <-- โฟลเดอร์สำหรับเก็บธีม ปลั๊กอิน และไฟล์อัปโหลด
        └── ...               <-- ไฟล์ระบบมาตรฐานของ WordPress Core</pre>

การเตรียมไฟล์ Docker Compose (docker-compose.yml)

ไฟล์นี้ใช้เชื่อมโยงคอนเทนเนอร์ app (PHP-FPM) และ web (Nginx) เข้าด้วยกันผ่าน Docker Network ภายนอกชื่อ magi

services:
  app:
    image: language_php_8_5_dev:8.5
    container_name: app_wordpress_71_dev
    restart: unless-stopped
    env_file:
      - ../.env
    volumes:
      - ./src:/var/www/html
    networks:
      - magi

  web:
    image: nginx:1.25
    container_name: web_wordpress_71_dev
    restart: unless-stopped
    ports:
      - "8082:80"
    volumes:
      - ./src:/var/www/html
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    networks:
      - magi

networks:
  magi:
    external: true

การตั้งค่า Nginx (nginx/default.conf)

สร้างไฟล์ nginx/default.conf เพื่อรับ Request พอร์ต 8082 จัดการ Static Cache และส่งต่อ PHP ไปยัง app:9000

server {
	listen 80;
	server_name localhost;
	root /var/www/html;
	index index.php index.html;

	location / {
		try_files $uri $uri/ /index.php?$args;
	}

	location ~ \.php$ {
		fastcgi_pass app:9000;
		fastcgi_index index.php;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		include fastcgi_params;
		fastcgi_buffer_size 16k;
		fastcgi_buffers 4 16k;
	}

	location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
		expires max;
		log_not_found off;
	}

	location ~* \.log$ {
		deny all;
		access_log off;
		log_not_found off;
	}
}


แม่แบบฐานข้อมูลเริ่มต้น (init.sql)

ไฟล์ init.sql ใช้เป็นเทมเพลตสร้างตารางหลักของ WordPress พร้อมสร้างบัญชีผู้ดูแลระบบ (Admin) โดยจะรองรับ Placeholder ตัวแปร เช่น __DB_NAME__, __ADMIN_USER__, __ADMIN_PASS__ เพื่อให้สคริปต์นำค่าจาก .env มาแทนที่ตอนรัน

SET NAMES utf8mb4;
SET sql_mode = '';

-- ==========================================
-- 1. Database & Privilege Setup
-- ==========================================
CREATE DATABASE IF NOT EXISTS __DB_NAME__ CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON __DB_NAME__.* TO '__DB_USER__'@'%';
FLUSH PRIVILEGES;

USE __DB_NAME__;

-- Table: __DB_PREFIX__options
CREATE TABLE IF NOT EXISTS __DB_PREFIX__options (
	option_id bigint(20) unsigned NOT NULL auto_increment,
	option_name varchar(191) NOT NULL default '',
	option_value longtext NOT NULL,
	autoload varchar(20) NOT NULL default 'yes',
	PRIMARY KEY  (option_id),
	UNIQUE KEY option_name (option_name),
	KEY autoload (autoload)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO __DB_PREFIX__options (option_name, option_value, autoload) VALUES
('siteurl', 'http://localhost:8082', 'yes'),
('home', 'http://localhost:8082', 'yes'),
('blogname', 'Local WordPress Development', 'yes'),
('admin_email', '__ADMIN_EMAIL__', 'yes'),
('users_can_register', '0', 'yes'),
('default_role', 'subscriber', 'yes'),
('db_version', '61833', 'yes'),
('initial_db_version', '61833', 'on'),
('admin_email_lifespan', '2147483647', 'on'),
('active_plugins', 'a:0:{}', 'yes'),
('sidebars_widgets', 'a:0:{}', 'yes'),
('template', 'twentytwentyfive', 'on'),
('stylesheet', 'twentytwentyfive', 'on')
ON DUPLICATE KEY UPDATE option_value=VALUES(option_value);

-- Table: __DB_PREFIX__users
CREATE TABLE IF NOT EXISTS __DB_PREFIX__users (
	ID bigint(20) unsigned NOT NULL auto_increment,
	user_login varchar(60) NOT NULL default '',
	user_pass varchar(255) NOT NULL default '',
	user_nicename varchar(50) NOT NULL default '',
	user_email varchar(100) NOT NULL default '',
	user_url varchar(100) NOT NULL default '',
	user_registered datetime NOT NULL default CURRENT_TIMESTAMP,
	user_activation_key varchar(255) NOT NULL default '',
	user_status int(11) NOT NULL default 0,
	display_name varchar(250) NOT NULL default '',
	PRIMARY KEY  (ID),
	KEY user_login_key (user_login),
	KEY user_nicename (user_nicename),
	KEY user_email (user_email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Table: __DB_PREFIX__usermeta
CREATE TABLE IF NOT EXISTS __DB_PREFIX__usermeta (
	umeta_id bigint(20) unsigned NOT NULL auto_increment,
	user_id bigint(20) unsigned NOT NULL default 0,
	meta_key varchar(255) default NULL,
	meta_value longtext default NULL,
	PRIMARY KEY  (umeta_id),
	KEY user_id (user_id),
	KEY meta_key (meta_key(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Table: __DB_PREFIX__posts
CREATE TABLE IF NOT EXISTS __DB_PREFIX__posts (
	ID bigint(20) unsigned NOT NULL auto_increment,
	post_author bigint(20) unsigned NOT NULL default 0,
	post_date datetime NOT NULL default '0000-00-00 00:00:00',
	post_date_gmt datetime NOT NULL default '0000-00-00 00:00:00',
	post_content longtext NOT NULL,
	post_title text NOT NULL,
	post_excerpt text NOT NULL,
	post_status varchar(20) NOT NULL default 'publish',
	comment_status varchar(20) NOT NULL default 'open',
	ping_status varchar(20) NOT NULL default 'open',
	post_password varchar(255) NOT NULL default '',
	post_name varchar(200) NOT NULL default '',
	to_send text NOT NULL,
	pinged text NOT NULL,
	post_modified datetime NOT NULL default '0000-00-00 00:00:00',
	post_modified_gmt datetime NOT NULL default '0000-00-00 00:00:00',
	post_content_filtered longtext NOT NULL,
	post_parent bigint(20) unsigned NOT NULL default 0,
	guid varchar(255) NOT NULL default '',
	menu_order int(11) NOT NULL default 0,
	post_type varchar(20) NOT NULL default 'post',
	post_mime_type varchar(100) NOT NULL default '',
	comment_count bigint(20) NOT NULL default 0,
	to_ping text NOT NULL,
	PRIMARY KEY  (ID),
	KEY post_name (post_name(191)),
	KEY type_status_date (post_type,post_status,post_date,ID),
	KEY post_parent (post_parent),
	KEY post_author (post_author),
	KEY type_status_author (post_type,post_status,post_author)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Table: __DB_PREFIX__comments
CREATE TABLE IF NOT EXISTS __DB_PREFIX__comments (
	comment_ID bigint(20) unsigned NOT NULL auto_increment,
	comment_post_ID bigint(20) unsigned NOT NULL default 0,
	comment_author tinytext NOT NULL,
	comment_author_email varchar(100) NOT NULL default '',
	comment_author_url varchar(200) NOT NULL default '',
	comment_author_IP varchar(100) NOT NULL default '',
	comment_date datetime NOT NULL default '0000-00-00 00:00:00',
	comment_date_gmt datetime NOT NULL default '0000-00-00 00:00:00',
	comment_content text NOT NULL,
	comment_approved varchar(20) NOT NULL default '1',
	comment_agent varchar(255) NOT NULL default '',
	comment_type varchar(20) NOT NULL default 'comment',
	comment_parent bigint(20) unsigned NOT NULL default 0,
	user_id bigint(20) unsigned NOT NULL default 0,
	comment_karma int(11) NOT NULL default 0,
	PRIMARY KEY  (comment_ID),
	KEY comment_post_ID (comment_post_ID),
	KEY comment_approved_date_gmt (comment_approved,comment_date_gmt),
	KEY comment_date_gmt (comment_date_gmt),
	KEY comment_parent (comment_parent),
	KEY comment_author_email (comment_author_email(10))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Table: __DB_PREFIX__links
CREATE TABLE IF NOT EXISTS __DB_PREFIX__links (
	link_id bigint(20) unsigned NOT NULL auto_increment,
	link_url varchar(255) NOT NULL default '',
	link_name varchar(255) NOT NULL default '',
	link_image varchar(255) NOT NULL default '',
	link_target varchar(25) NOT NULL default '',
	link_description varchar(255) NOT NULL default '',
	link_visible varchar(20) NOT NULL default 'Y',
	link_owner bigint(20) unsigned NOT NULL default 1,
	link_rating int(11) NOT NULL default 0,
	link_updated datetime NOT NULL default '0000-00-00 00:00:00',
	link_rel varchar(255) NOT NULL default '',
	link_notes mediumtext NOT NULL,
	link_rss varchar(255) NOT NULL default '',
	PRIMARY KEY  (link_id),
	KEY link_visible (link_visible)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Table: __DB_PREFIX__postmeta
CREATE TABLE IF NOT EXISTS __DB_PREFIX__postmeta (
	meta_id bigint(20) unsigned NOT NULL auto_increment,
	post_id bigint(20) unsigned NOT NULL default 0,
	meta_key varchar(255) default NULL,
	meta_value longtext default NULL,
	PRIMARY KEY  (meta_id),
	KEY post_id (post_id),
	KEY meta_key (meta_key(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Table: __DB_PREFIX__terms
CREATE TABLE IF NOT EXISTS __DB_PREFIX__terms (
	term_id bigint(20) unsigned NOT NULL auto_increment,
	name varchar(200) NOT NULL default '',
	slug varchar(200) NOT NULL default '',
	term_group bigint(10) NOT NULL default 0,
	PRIMARY KEY  (term_id),
	KEY slug (slug(191)),
	KEY name (name(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Table: __DB_PREFIX__term_taxonomy
CREATE TABLE IF NOT EXISTS __DB_PREFIX__term_taxonomy (
	term_taxonomy_id bigint(20) unsigned NOT NULL auto_increment,
	term_id bigint(20) unsigned NOT NULL default 0,
	taxonomy varchar(32) NOT NULL default '',
	description longtext NOT NULL,
	parent bigint(20) unsigned NOT NULL default 0,
	count bigint(20) NOT NULL default 0,
	PRIMARY KEY  (term_taxonomy_id),
	UNIQUE KEY term_id_taxonomy (term_id,taxonomy),
	KEY taxonomy (taxonomy)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Table: __DB_PREFIX__term_relationships
CREATE TABLE IF NOT EXISTS __DB_PREFIX__term_relationships (
	object_id bigint(20) unsigned NOT NULL default 0,
	term_taxonomy_id bigint(20) unsigned NOT NULL default 0,
	term_order int(11) NOT NULL default 0,
	PRIMARY KEY  (object_id,term_taxonomy_id),
	KEY term_taxonomy_id (term_taxonomy_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Table: __DB_PREFIX__termmeta
CREATE TABLE IF NOT EXISTS __DB_PREFIX__termmeta (
	meta_id bigint(20) unsigned NOT NULL auto_increment,
	term_id bigint(20) unsigned NOT NULL default 0,
	meta_key varchar(255) default NULL,
	meta_value longtext default NULL,
	PRIMARY KEY  (meta_id),
	KEY term_id (term_id),
	KEY meta_key (meta_key(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Table: __DB_PREFIX__commentmeta
CREATE TABLE IF NOT EXISTS __DB_PREFIX__commentmeta (
	meta_id bigint(20) unsigned NOT NULL auto_increment,
	comment_post_ID bigint(20) unsigned NOT NULL default 0,
	meta_key varchar(255) default NULL,
	meta_value longtext default NULL,
	comment_id bigint(20) unsigned NOT NULL default 0,
	PRIMARY KEY  (meta_id),
	KEY comment_post_ID (comment_post_ID),
	KEY meta_key (meta_key(191)),
	KEY comment_id (comment_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ==========================================
-- 3. Pre-seed Administrator User & Posts
-- ==========================================
INSERT INTO __DB_PREFIX__users (ID, user_login, user_pass, user_nicename, user_email, user_registered, display_name)
VALUES (1, '__ADMIN_USER__', MD5('__ADMIN_PASS_HASH__'), '__ADMIN_USER__', '__ADMIN_EMAIL__', NOW(), '__ADMIN_USER__')
ON DUPLICATE KEY UPDATE user_pass=VALUES(user_pass), user_email=VALUES(user_email);

INSERT INTO __DB_PREFIX__usermeta (user_id, meta_key, meta_value) VALUES
(1, 'nickname', '__ADMIN_USER__'),
(1, 'first_name', ''),
(1, 'last_name', ''),
(1, 'rich_editing', 'true'),
(1, 'comment_shortcuts', 'false'),
(1, 'admin_color', 'fresh'),
(1, 'use_ssl', '0'),
(1, 'show_admin_bar_front', 'true')
ON DUPLICATE KEY UPDATE meta_value=VALUES(meta_value);

INSERT INTO __DB_PREFIX__posts (ID, post_author, post_date, post_date_gmt, post_content, post_title, post_excerpt, post_status, comment_status, ping_status, post_name, to_send, pinged, post_modified, post_modified_gmt, guid, post_type, to_ping)
VALUES (1, 1, NOW(), NOW(), 'Welcome to WordPress. This is your first post.', 'Hello world!', '', 'publish', 'open', 'open', 'hello-world', '', '', NOW(), NOW(), 'http://localhost:8082/?p=1', 'post', '')
ON DUPLICATE KEY UPDATE post_title=VALUES(post_title);


การสร้างสคริปต์ BUILD อัตโนมัติ (build.sh)

ใช้ Bash Script จัดการดาวน์โหลด Core WordPress, สร้างเกลือความปลอดภัย (Salt Keys), และตั้งค่า wp-config.php ให้พร้อมใช้งาน

#!/usr/bin/env bash
set -eo pipefail

CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BASE_DIR="$(cd "${CURRENT_DIR}/.." && pwd)"
ENV_FILE="${BASE_DIR}/.env"
SQL_TEMPLATE="${CURRENT_DIR}/init.sql"
PROCESSED_SQL="${CURRENT_DIR}/processed_init.sql"
SRC_DIR="${CURRENT_DIR}/src"
WP_CONFIG_FILE="${SRC_DIR}/wp-config.php"

echo "==> [Step 1] Loading environment variables from .env..."
if [ ! -f "$ENV_FILE" ]; then
	echo "Error: Central .env file not found at $ENV_FILE" >&2
	exit 1
fi

TEMP_ENV=$(mktemp)
tr -d '\r' < "$ENV_FILE" > "$TEMP_ENV"
set -a
source "$TEMP_ENV"
set +a
rm -f "$TEMP_ENV"

# -------------------------------------------------------------
# 1. Resolve and EXPORT Core Variables for Python
# -------------------------------------------------------------
export WP_CONFIG_FILE
export TARGET_DB_NAME="${WP_DB_NAME:-${DB_DATABASE:-${DB_NAME:-WP71_DEV}}}"
export TARGET_DB_USER="${DB_USER:-db_admin}"
export TARGET_DB_PASSWORD="${DB_PASSWORD:-}"
export TARGET_DB_HOST="${WP_DB_HOST:-db_mariadb_13}"
export TARGET_DB_CHARSET="${WP_DB_CHARSET:-utf8mb4}"
export TARGET_DB_COLLATE="${WP_DB_COLLATE:-}"
export TARGET_TABLE_PREFIX="${WP_DB_PREFIX:-wp_}"
export ROOT_PASSWORD="${DB_ROOT_PASSWORD:-}"

ADMIN_EMAIL="${WP_ADMIN_EMAIL:[email protected]}"
ADMIN_USER="${WP_ADMIN_USER:-admin}"
ADMIN_PASS="${WP_ADMIN_PASSWORD:-}"

# -------------------------------------------------------------
# 2. Check and Prepare WordPress Core Files
# -------------------------------------------------------------
echo "==> [Step 2] Checking WordPress source files in src/..."
mkdir -p "$SRC_DIR"

if [ ! -f "$SRC_DIR/index.php" ]; then
	echo "WordPress core files not found in src/. Downloading latest version..."
	curl -sSL -O https://wordpress.org/latest.tar.gz
	tar -xzf latest.tar.gz --strip-components=1 -C "$SRC_DIR/"
	rm -f latest.tar.gz
	echo "WordPress core files successfully extracted."
fi

if [ ! -f "$WP_CONFIG_FILE" ]; then
	if [ -f "$SRC_DIR/wp-config-sample.php" ]; then
		echo "Creating wp-config.php from sample template..."
		cp "$SRC_DIR/wp-config-sample.php" "$WP_CONFIG_FILE"
	else
		echo "Error: Neither wp-config.php nor wp-config-sample.php was found in $SRC_DIR" >&2
		exit 1
	fi
fi

# -------------------------------------------------------------
# 3. Synchronize wp-config.php (Database, Salts, & WP_* Constants)
# -------------------------------------------------------------
echo "==> [Step 3] Configuring wp-config.php with .env and security salts..."
chmod 666 "$WP_CONFIG_FILE" 2>/dev/null || true

python3 - << 'PYEOF'
import os, re, secrets, string

config_path = os.environ.get('WP_CONFIG_FILE', 'src/wp-config.php')
with open(config_path, 'r', encoding='utf-8') as f:
	content = f.read()

# 1. Update Database Credentials
standard_replacements = {
	'DB_NAME': os.environ.get('TARGET_DB_NAME', 'WP71_DEV'),
	'DB_USER': os.environ.get('TARGET_DB_USER', 'db_admin'),
	'DB_PASSWORD': os.environ.get('TARGET_DB_PASSWORD', ''),
	'DB_HOST': os.environ.get('TARGET_DB_HOST', 'db_mariadb_13'),
	'DB_CHARSET': os.environ.get('TARGET_DB_CHARSET', 'utf8mb4'),
	'DB_COLLATE': os.environ.get('TARGET_DB_COLLATE', '')
}

for key, val in standard_replacements.items():
	pattern = r"define\(\s*['\"]" + re.escape(key) + r"['\"]\s*,\s*['\"].*?['\"]\s*\);"
	replace = f"define( '{key}', '{val}' );"
	content = re.sub(pattern, replace, content)

# 2. Update Table Prefix
prefix = os.environ.get('TARGET_TABLE_PREFIX', 'wp_')
content = re.sub(r"\$table_prefix\s*=\s*['\"].*?['\"];", f"$table_prefix = '{prefix}';", content)

# 3. Generate Security Salts if using default sample phrases
salt_keys = [
	'AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'NONCE_KEY',
	'AUTH_SALT', 'SECURE_AUTH_SALT', 'LOGGED_IN_SALT', 'NONCE_SALT'
]
chars = string.ascii_letters + string.digits + '!#$%&*+-.:;=?@^_~'

for key in salt_keys:
	pattern = r"define\(\s*['\"]" + re.escape(key) + r"['\"]\s*,\s*['\"]put your unique phrase here['\"]\s*\);"
	if re.search(pattern, content):
		new_salt = ''.join(secrets.choice(chars) for _ in range(64))
		content = re.sub(pattern, f"define( '{key}', '{new_salt}' );", content)

# 4. Dynamically Inject / Update all WP_* variables from .env
skip_keys = {
	'WP_DB_NAME', 'WP_DB_USER', 'WP_DB_PASSWORD', 'WP_DB_HOST',
	'WP_DB_CHARSET', 'WP_DB_COLLATE', 'WP_DB_PREFIX',
	'WP_ADMIN_USER', 'WP_ADMIN_PASSWORD', 'WP_ADMIN_EMAIL'
}

dynamic_wp = {k: v for k, v in os.environ.items() if k.startswith('WP_') and k not in skip_keys}

for key, val in dynamic_wp.items():
	val_clean = val.strip()
	val_lower = val_clean.lower()
	
	if val_lower in ['true', 'false']:
		formatted_val = val_lower
	elif val_clean.isdigit():
		formatted_val = val_clean
	else:
		formatted_val = f"'{val_clean}'"
		
	pattern = r"define\(\s*['\"]" + re.escape(key) + r"['\"]\s*,\s*.*?\s*\);"
	replacement = f"define( '{key}', {formatted_val} );"
	
	if re.search(pattern, content):
		content = re.sub(pattern, replacement, content)
	else:
		marker = "/* That's all, stop editing!"
		if marker in content:
			content = content.replace(marker, f"{replacement}\n{marker}")
		else:
			content += f"\n{replacement}\n"

with open(config_path, 'w', encoding='utf-8') as f:
	f.write(content)
PYEOF

echo "wp-config.php successfully updated."

# -------------------------------------------------------------
# 4. Process init.sql Template
# -------------------------------------------------------------
echo "==> [Step 4] Processing init.sql template..."
if [ -f "$SQL_TEMPLATE" ]; then
	tr -d '\r' < "$SQL_TEMPLATE" | sed \
		-e "s|__DB_NAME__|${TARGET_DB_NAME}|g" \
		-e "s|__DB_USER__|${TARGET_DB_USER}|g" \
		-e "s|__DB_PREFIX__|${TARGET_TABLE_PREFIX}|g" \
		-e "s|__ADMIN_EMAIL__|${ADMIN_EMAIL}|g" \
		-e "s|__ADMIN_USER__|${ADMIN_USER}|g" \
		-e "s|__ADMIN_PASS_HASH__|${ADMIN_PASS}|g" \
		> "$PROCESSED_SQL"
else
	echo "Error: init.sql not found at $SQL_TEMPLATE" >&2
	exit 1
fi

# -------------------------------------------------------------
# 5. Boot Container Fleet
# -------------------------------------------------------------
echo "==> [Step 5] Starting containers via run.sh..."
if [ -f "$CURRENT_DIR/run.sh" ]; then
	bash "$CURRENT_DIR/run.sh"
else
	echo "Error: run.sh not found." >&2
	exit 1
fi

# -------------------------------------------------------------
# 6. Wait for Database Readiness & Deploy Initial Schema
# -------------------------------------------------------------
echo "==> [Step 6] Waiting for MariaDB service to accept connections..."
until docker exec db_mariadb_13 mariadb-admin ping -u root -p"${ROOT_PASSWORD}" --silent 2>/dev/null; do
	sleep 2
	echo -n "."
done
echo " MariaDB is ready."

echo "==> [Step 7] Initializing database '${TARGET_DB_NAME}' via root privilege..."
docker exec -e MYSQL_PWD="${ROOT_PASSWORD}" -i db_mariadb_13 mariadb -u root < "$PROCESSED_SQL"
rm -f "$PROCESSED_SQL"

# -------------------------------------------------------------
# 7. WordPress Core Bootstrap: Initialize Roles & Assign Admin
# -------------------------------------------------------------
echo "==> [Step 8] Initializing WordPress Core Roles & Admin Capabilities..."
docker exec -i app_wordpress_71_dev php -r "
require_once('/var/www/html/wp-load.php');
require_once('/var/www/html/wp-admin/includes/schema.php');
populate_roles();
\$u = new WP_User(1);
if (\$u->exists()) {
	\$u->set_role('administrator');
}
wp_cache_flush();
echo 'WordPress Roles and Admin Capabilities initialized successfully.' . PHP_EOL;
"

echo "==> Build and Environment Setup Completed Successfully!"


การสร้างสคริปต์ RUN อัตโนมัติ (run.sh)

สคริปต์สำหรับเริ่ม Container และแปลงเทมเพลต init.sql ไป Import เข้า MariaDB อัตโนมัติ

#!/usr/bin/env bash
set -eo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BASE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
cd "${SCRIPT_DIR}"

# 1. โหลด Environment Variables จากไฟล์กลาง (.env)
ENV_FILE="${BASE_DIR}/.env"
if [ -f "${ENV_FILE}" ]; then
	set -a
	source "${ENV_FILE}"
	set +a
fi

# กำหนดตัวแปรสำหรับจัดการเวอร์ชัน MariaDB
MARIADB_VERSION="${MARIADB_VERSION:-13}"
MARIADB_DIR="${BASE_DIR}/db_mariadb_${MARIADB_VERSION}"

# 2. ตรวจสอบและสร้าง Network 'magi'
echo "==> [Step 1] Ensuring Docker network 'magi' exists..."
if ! docker network inspect magi >/dev/null 2>&1; then
	docker network create magi
	echo "Created network: magi"
else
	echo "Network 'magi' is ready."
fi

# 3. สตาร์ท MariaDB Service จากโฟลเดอร์กลาง
echo "==> [Step 2] Starting MariaDB service (Version: ${MARIADB_VERSION})..."
if [ -d "${MARIADB_DIR}" ]; then
	docker compose -f "${MARIADB_DIR}/docker-compose.yml" up -d --remove-orphans
else
	echo "Error: MariaDB directory not found at ${MARIADB_DIR}" >&2
	exit 1
fi

# 4. สตาร์ท WordPress Environment ของโปรเจกต์นี้
echo "==> [Step 3] Starting WordPress environment..."
docker compose up -d --force-recreate --remove-orphans

ขั้นตอนการสั่งรันระบบ (How to Run)

  1. สร้าง Docker Network magi หากยังไม่ได้สร้างในเครื่อง
    docker network create magi
  2. ลบ DATA ที่ตกค้างหรือไม่ได้ถูกเรียกใช้งาน (ถ้ามี)
    cd /Users/common/Docker/db_mariadb_13
    docker compose down
    rm -rf data/*
  3. เข้าไปยังโฟลเดอร์ MariaDB และสั่งรันสคริปต์เตรียมระบบ:
    cd /Users/common/Docker/db_mariadb_13
    ./build.sh
  4. สั่งเริ่มการทำงานของคอนเทนเนอร์
    docker compose up -d
  5. ตรวจสอบสถานะคอนเทนเนอร์
    docker ps
    คอนเทนเนอร์ db_mariadb_13 จะเริ่มทำงานและพร้อมให้บริการแก่แอปพลิเคชันทุกตัวในเครือข่าย magi ทันที

อ่านเพิ่มเติม