Install
openclaw skills install wordpress-trade-siteInteractive guide to deploy a production-ready WordPress site for international trade businesses. Triggers on "build a trade website", "deploy wordpress trade site", "set up B2B website", or similar requests.
openclaw skills install wordpress-trade-siteDeploy a production-ready WordPress trade site from scratch through 9 interactive phases. Based on the wordpress-trade-starter template.
Principle: Automate everything possible. Only pause when user decisions or information are required. When errors occur, diagnose and fix them proactively — don't tell the user to go fix things themselves.
Interaction: Use AskUserQuestion for all user-facing questions. Report progress after each phase, then continue to the next.
Data flow: Information collected in each phase (business info, SSH details, domain, etc.) is reused in subsequent phases. Key data is labeled with variable names for reference.
Before any technical work, understand the user's business needs.
AskUserQuestion (collect sequentially):
Company name — "What is your company name? (Both local language and English)"
COMPANY_EN (and COMPANY_LOCAL if provided)Main products — "What products do you mainly sell? (Brief description)"
PRODUCTSTarget markets — "Which markets do you primarily target?" (multiSelect)
TARGET_MARKETSLanguages — "Which languages should your website support?" (multiSelect, recommend based on target markets)
LANGUAGESContact info — "How should customers reach you?"
WHATSAPP and EMAILSummary confirmation: Display collected information and AskUserQuestion to confirm correctness.
AskUserQuestion: "Do you already have a Linux server?"
TARGET_MARKETSPurchase guidance (if needed):
Recommend based on target market:
| Target Market | Recommended DC | Recommended Providers |
|---|---|---|
| Europe/Africa | Germany/Netherlands | Contabo, Hetzner |
| North America | US East/West Coast | DigitalOcean, Vultr |
| Southeast Asia | Singapore | Vultr, Alibaba Cloud |
| Middle East | Dubai/Bahrain | AWS, Alibaba Cloud |
| Latin America | US Miami | DigitalOcean |
Recommended specs: 2 cores / 2GB RAM / 40GB SSD ($10-20/month). Tell user to continue after purchase.
AskUserQuestion: "Please provide your server SSH details:"
SERVER_IPSSH_PORTSSH_USERVerify connection:
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -p ${SSH_PORT} ${SSH_USER}@${SERVER_IP} "echo CONNECTION_OK && uname -a && free -m && df -h /"
Execute via SSH:
# System update
apt update && apt upgrade -y
# Set timezone
timedatectl set-timezone UTC
# Create swap (if RAM <= 2GB and no swap exists)
if [ $(free -m | awk '/^Mem:/{print $2}') -le 2048 ] && [ ! -f /swapfile ]; then
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
fi
# Firewall
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
# Essential tools
apt install -y curl git unzip htop
Verify: ufw status shows 22/80/443 open.
if ! command -v docker &>/dev/null; then
curl -fsSL https://get.docker.com | sh
systemctl enable docker && systemctl start docker
fi
docker compose version || apt-get install -y docker-compose-plugin
Verify: docker compose version outputs a version number.
cd /opt
git clone https://github.com/iPythoning/wordpress-trade-starter.git wordpress
cd /opt/wordpress
AskUserQuestion: "Please provide the following:"
DOMAINMYSQL_ROOT_PASSWORDMYSQL_PASSWORDIf user chooses auto-generation:
MYSQL_ROOT_PASSWORD=$(openssl rand -base64 16)
MYSQL_PASSWORD=$(openssl rand -base64 16)
Write .env:
cat > /opt/wordpress/.env << EOF
MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=${MYSQL_PASSWORD}
DOMAIN=${DOMAIN}
EMAIL=${EMAIL}
EOF
Important: Display the generated passwords to the user and remind them to save securely.
sed -i "s/YOUR_DOMAIN/${DOMAIN}/g" /opt/wordpress/nginx.conf
cd /opt/wordpress
docker compose up -d
Verify:
docker compose ps
# Confirm all three containers (db, wordpress, nginx) are running/healthy
Wait for WordPress to be ready:
for i in $(seq 1 30); do
curl -sf http://localhost > /dev/null 2>&1 && break
sleep 2
done
docker compose logs db, common cause is password format issuesdocker compose logs wordpressAskUserQuestion: "Which SSL certificate method?"
Prerequisite: Domain A records must point to SERVER_IP. If not configured, guide the user to add in their DNS panel:
DOMAIN → A → SERVER_IPwww.DOMAIN → A → SERVER_IP# Stop nginx to free port 80
cd /opt/wordpress && docker compose stop nginx
apt install -y certbot
certbot certonly --standalone -d ${DOMAIN} -d www.${DOMAIN} --email ${EMAIL} --agree-tos --non-interactive
mkdir -p /opt/wordpress/ssl
cp /etc/letsencrypt/live/${DOMAIN}/fullchain.pem /opt/wordpress/ssl/
cp /etc/letsencrypt/live/${DOMAIN}/privkey.pem /opt/wordpress/ssl/
# Auto-renewal cron
(crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --pre-hook 'cd /opt/wordpress && docker compose stop nginx' --post-hook 'cp /etc/letsencrypt/live/${DOMAIN}/fullchain.pem /opt/wordpress/ssl/ && cp /etc/letsencrypt/live/${DOMAIN}/privkey.pem /opt/wordpress/ssl/ && cd /opt/wordpress && docker compose start nginx'") | crontab -
# Restart nginx
docker compose start nginx
Guide the user through Cloudflare Dashboard:
mkdir -p /opt/wordpress/ssl
# User pastes certificate content
cat > /opt/wordpress/ssl/fullchain.pem << 'EOF'
<user pastes certificate here>
EOF
cat > /opt/wordpress/ssl/privkey.pem << 'EOF'
<user pastes private key here>
EOF
docker compose restart nginx
curl -I https://${DOMAIN} 2>/dev/null | head -5
Tell the user to open https://${DOMAIN}/wp-admin/install.php in their browser and fill in:
COMPANY_ENEMAILAskUserQuestion: "Have you completed the WordPress setup wizard?"
docker compose exec wordpress bash -c '
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
mv wp-cli.phar /usr/local/bin/wp
'
Verify: docker compose exec wordpress wp --info --allow-root
docker compose exec wordpress wp --allow-root option update blogname "${COMPANY_EN}"
docker compose exec wordpress wp --allow-root option update blogdescription "${PRODUCTS} - ${COMPANY_EN}"
docker compose exec wordpress wp --allow-root option update timezone_string "UTC"
docker compose exec wordpress wp --allow-root option update date_format "Y-m-d"
docker compose exec wordpress wp --allow-root option update permalink_structure "/%postname%/"
# Delete default content
docker compose exec wordpress wp --allow-root post delete 1 2 --force
docker compose exec wordpress wp --allow-root widget reset --all
docker compose exec wordpress wp --allow-root theme install astra --activate
# Create Child Theme
docker compose exec wordpress bash -c '
mkdir -p /var/www/html/wp-content/themes/astra-child
cat > /var/www/html/wp-content/themes/astra-child/style.css << "CSS"
/*
Theme Name: Astra Child
Template: astra
Version: 1.0.0
*/
CSS
cat > /var/www/html/wp-content/themes/astra-child/functions.php << "PHP"
<?php
add_action("wp_enqueue_scripts", function() {
wp_enqueue_style("parent-style", get_template_directory_uri() . "/style.css");
});
PHP
'
docker compose exec wordpress wp --allow-root theme activate astra-child
Install plugins one by one to track failures:
PLUGINS="elementor seo-by-rank-math wp-super-cache imagify jetpack-boost polylang contact-form-7 flamingo chaty ecommerce-product-catalog"
for plugin in $PLUGINS; do
docker compose exec wordpress wp --allow-root plugin install $plugin --activate 2>&1 || echo "WARN: $plugin install failed, manual install needed"
done
Note: Some plugins may not be in the wordpress.org directory. For failed installs, inform the user to install manually from the admin panel (Plugins → Add New).
Critical: CF7 only sends emails by default — if the recipient email is wrong, all inquiries are lost permanently. Flamingo stores every submission in the WP database as a safety net.
After installing, verify:
# Verify Flamingo is active (auto-stores CF7 submissions)
docker compose exec wordpress wp --allow-root plugin list --status=active --field=name | grep flamingo
# Update CF7 form recipient to the admin email (not a non-existent address)
docker compose exec wordpress wp --allow-root post list --post_type=wpcf7_contact_form --fields=ID,post_title
# For each form, verify the recipient matches EMAIL:
docker compose exec wordpress wp --allow-root post meta get <FORM_ID> _mail
# If recipient is wrong, update it via WP Admin → Contact → Forms → Edit
Inquiries are viewable at: WP Admin → Flamingo → Inbound Messages.
AskUserQuestion: "Do you have an Imagify API key? (Free signup at imagify.io, 20MB/month free tier)"
IMAGIFY_API_KEYdocker compose exec wordpress wp --allow-root super-cache enable 2>/dev/null || true
If WP-CLI doesn't support it, tell the user to go to Settings → WP Super Cache:
Tell the user to enable in the admin panel:
Configure based on the LANGUAGES list from Phase 1:
# Set English as default language
docker compose exec wordpress wp --allow-root pll lang create "English" en en_US
Add each selected language:
wp pll lang create "Chinese" zh zh_CNwp pll lang create "Russian" ru ru_RUwp pll lang create "Spanish" es es_ESwp pll lang create "French" fr fr_FRwp pll lang create "Arabic" ar arNote: Polylang WP-CLI support may be limited. If commands fail, guide the user to add languages manually in Languages → Settings.
Guide the user through the Rank Math setup wizard:
COMPANY_ENhttps://${DOMAIN}docker compose exec wordpress bash -c '
wp --allow-root post create --post_type=page --post_title="Home" --post_status=publish --post_content="<!-- wp:paragraph --><p>Welcome to '"${COMPANY_EN}"'. We specialize in '"${PRODUCTS}"'.</p><!-- /wp:paragraph -->"
wp --allow-root post create --post_type=page --post_title="About Us" --post_status=publish --post_content="<!-- wp:paragraph --><p>About '"${COMPANY_EN}"'</p><!-- /wp:paragraph -->"
wp --allow-root post create --post_type=page --post_title="Products" --post_status=publish --post_content="<!-- wp:paragraph --><p>Our product catalog</p><!-- /wp:paragraph -->"
wp --allow-root post create --post_type=page --post_title="Contact" --post_status=publish --post_content="<!-- wp:paragraph --><p>Get in touch with us. WhatsApp: '"${WHATSAPP}"' Email: '"${EMAIL}"'</p><!-- /wp:paragraph -->[contact-form-7]"
wp --allow-root post create --post_type=page --post_title="FAQ" --post_status=publish --post_content="<!-- wp:paragraph --><p>Frequently Asked Questions</p><!-- /wp:paragraph -->"
wp --allow-root post create --post_type=page --post_title="Blog" --post_status=publish --post_content=""
'
# Set Home as front page, Blog as posts page
HOME_ID=$(docker compose exec wordpress wp --allow-root post list --post_type=page --name=home --field=ID)
BLOG_ID=$(docker compose exec wordpress wp --allow-root post list --post_type=page --name=blog --field=ID)
docker compose exec wordpress wp --allow-root option update show_on_front page
docker compose exec wordpress wp --allow-root option update page_on_front $HOME_ID
docker compose exec wordpress wp --allow-root option update page_for_posts $BLOG_ID
docker compose exec wordpress bash -c '
wp --allow-root menu create "Main Menu"
wp --allow-root menu item add-post "Main Menu" $(wp --allow-root post list --post_type=page --name=home --field=ID) --title="Home"
wp --allow-root menu item add-post "Main Menu" $(wp --allow-root post list --post_type=page --name=about-us --field=ID) --title="About"
wp --allow-root menu item add-post "Main Menu" $(wp --allow-root post list --post_type=page --name=products --field=ID) --title="Products"
wp --allow-root menu item add-post "Main Menu" $(wp --allow-root post list --post_type=page --name=faq --field=ID) --title="FAQ"
wp --allow-root menu item add-post "Main Menu" $(wp --allow-root post list --post_type=page --name=blog --field=ID) --title="Blog"
wp --allow-root menu item add-post "Main Menu" $(wp --allow-root post list --post_type=page --name=contact --field=ID) --title="Contact"
wp --allow-root menu location assign "Main Menu" primary
'
AskUserQuestion: "Are you using Cloudflare for DNS management?"
Cloudflare configuration guide:
DOMAIN in Cloudflare Dashboard@ → A → SERVER_IP (Proxied)www → CNAME → DOMAIN (Proxied)In Cloudflare Dashboard:
In Cloudflare Dashboard → Caching:
Page Rules (if free quota available):
*${DOMAIN}/wp-admin/* → Cache Level: Bypass*${DOMAIN}/wp-login.php* → Cache Level: Bypass*${DOMAIN}/* → Cache Level: Cache Everything, Edge TTL: 2 hours# Test 1: Check Nginx proxy cache
curl -I https://${DOMAIN} 2>/dev/null | grep -i x-cache-status
# Expected: X-Cache-Status: HIT (on second request)
# Test 2: Check Cloudflare cache
curl -I https://${DOMAIN} 2>/dev/null | grep -i cf-cache-status
# Expected: cf-cache-status: HIT
# Test 3: Check WP Super Cache
docker compose exec wordpress ls /var/www/html/wp-content/cache/supercache/ 2>/dev/null && echo "SUPER_CACHE_OK" || echo "SUPER_CACHE_EMPTY"
Tell the user to visit Google PageSpeed Insights: https://pagespeed.web.dev/analysis?url=https://${DOMAIN}
Target metrics:
# Copy GEO functions to child theme
docker compose exec wordpress bash -c '
curl -sL https://raw.githubusercontent.com/iPythoning/wordpress-trade-starter/main/assets/geo-functions.php \
-o /var/www/html/wp-content/themes/astra-child/geo-functions.php
'
Add configuration to child theme's functions.php:
docker compose exec wordpress bash -c '
cat >> /var/www/html/wp-content/themes/astra-child/functions.php << "GEO"
// GEO Configuration
define("GEO_SAMEAS", []); // Add Wikipedia, LinkedIn, YouTube URLs later
define("GEO_LANGUAGES", [${LANGUAGES_ARRAY}]);
define("GEO_CONTACT_EMAIL", "${EMAIL}");
define("GEO_CONTACT_PHONE", "${WHATSAPP}");
require_once get_stylesheet_directory() . "/geo-functions.php";
GEO
'
AskUserQuestion: "Do you have profiles on these platforms? (We'll add them to your schema for AI recognition)" (multiSelect)
For each selected platform, collect the URL and add to GEO_SAMEAS array.
# Check robots.txt has AI bots
curl -s https://${DOMAIN}/robots.txt | grep -c "User-agent:"
# Expected: 15+ entries (standard + AI tier 1 + tier 2 + SEO bots)
# Check llms.txt auto-generation
curl -s https://${DOMAIN}/llms.txt | head -5
# Expected: "# COMPANY_NAME" followed by description
# Check JSON-LD schema
curl -s https://${DOMAIN}/ | grep -o '"@type":"[^"]*"' | sort -u
# Expected: Organization, WebSite, BreadcrumbList
Critical: Cloudflare's "Managed robots.txt" blocks AI crawlers by default.
Tell the user: Go to Cloudflare Dashboard → Security → Bots → Disable "Managed robots.txt".
Without this, all GEO work is nullified.
# WordPress file permission best practices
docker compose exec wordpress bash -c '
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;
chmod 440 /var/www/html/wp-config.php
'
Already handled in nginx.conf via skip_cache rules. Additional blocking:
docker compose exec wordpress bash -c '
cat >> /var/www/html/.htaccess << "HTACCESS"
# Block xmlrpc.php
<Files xmlrpc.php>
Require all denied
</Files>
HTACCESS
'
# Database backup script
cat > /opt/wordpress/backup.sh << 'BACKUP'
#!/bin/bash
BACKUP_DIR="/opt/wordpress/backups"
mkdir -p $BACKUP_DIR
DATE=$(date +%Y%m%d_%H%M%S)
docker compose -f /opt/wordpress/docker-compose.yml exec -T db mysqldump -u root -p${MYSQL_ROOT_PASSWORD} wordpress > $BACKUP_DIR/db_$DATE.sql
gzip $BACKUP_DIR/db_$DATE.sql
# Keep only last 7 days of backups
find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete
BACKUP
chmod +x /opt/wordpress/backup.sh
# Daily backup at 2am
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/wordpress/backup.sh") | crontab -
AskUserQuestion: "Would you like to set up free UptimeRobot monitoring?"
https://${DOMAIN}EMAILRun checks and report:
echo "=== WordPress Trade Site Verification Checklist ==="
# 1. Container status
docker compose -f /opt/wordpress/docker-compose.yml ps --format "table {{.Name}}\t{{.Status}}"
# 2. HTTPS
curl -sI https://${DOMAIN} | head -1
# 3. WordPress version
docker compose -f /opt/wordpress/docker-compose.yml exec wordpress wp --allow-root core version
# 4. Active plugins
docker compose -f /opt/wordpress/docker-compose.yml exec wordpress wp --allow-root plugin list --status=active --format=table
# 5. Published pages
docker compose -f /opt/wordpress/docker-compose.yml exec wordpress wp --allow-root post list --post_type=page --post_status=publish --format=table
# 6. SSL certificate expiry
echo | openssl s_client -connect ${DOMAIN}:443 -servername ${DOMAIN} 2>/dev/null | openssl x509 -noout -dates
# 7. Cache status
curl -sI https://${DOMAIN} | grep -iE "x-cache-status|cf-cache-status"
Output a summary report:
All 3 containers running (WordPress + MySQL + Nginx)
HTTPS certificate valid, expires: YYYY-MM-DD
WordPress 6.7, X active plugins installed
6 base pages created + navigation menu
Multilingual configured with X languages
SEO (Rank Math) enabled
Triple-layer cache active
Database auto-backup configured
xmlrpc.php blocked
File permissions hardened
GEO: robots.txt has AI bot tiers, llms.txt serving, JSON-LD schemas active
Site URL: https://${DOMAIN}
Admin URL: https://${DOMAIN}/wp-admin
Email: ${EMAIL}
WhatsApp: ${WHATSAPP}
Docker containers fail to start: Check docker compose logs <service>. Common issues: port already in use (lsof -i :80), insufficient disk space (df -h).
SSL certificate acquisition fails: Confirm DNS has propagated (dig ${DOMAIN}), port 80 is accessible. Let's Encrypt limits to 5 certificates per domain per week.
WordPress setup wizard doesn't appear: Check docker compose logs wordpress. If database connection fails, check if .env passwords contain special characters (need escaping).
Plugin installation fails: When WP-CLI fails, install manually from the admin panel (Plugins → Add New). Ensure the WordPress container can reach wordpress.org.
Low PageSpeed score: Confirm all three cache layers are active, Imagify has optimized existing images, Jetpack Boost Critical CSS has been generated. Clear all caches and retest.
SSH connection drops: Use screen or tmux for long-running commands. Reconnect with screen -r to resume session.