Overview
Following the Pterodactyl "egg" pattern, WordPress sites are deployed with:
- Fresh WordPress core downloaded on each deployment
- Configuration generated from environment variables
- Content (wp-content) stored on DigitalOcean Spaces CDN
- Minimal GitHub repo containing only deployment scripts
Repository Structure
wordpress-{sitename}/
├── .docker/
│ ├── init.sh # Container initialization script
│ ├── create-wp-config-from-env.php # wp-config generator
│ └── Dockerfile # Container definition (App Platform)
├── .do/
│ └── app.yaml # DigitalOcean App Platform spec
├── .gitignore # Exclude WordPress core files
├── README.md # Site documentation
└── sync-content-to-bucket.sh # Upload wp-content to DO Spaces
What's NOT in the Repo
- ❌ WordPress core files (wp-admin, wp-includes, etc.)
- ❌ wp-config.php (generated from env vars)
- ❌ wp-content/* (stored on DO Spaces bucket)
- ❌ Plugins, themes, uploads (on CDN)
Deployment Flow
1. Container Starts
App Platform deploys → Docker container initializes
2. Initialization Script Runs
.docker/init.sh executes:
1. Download latest WordPress core (if not present)
2. Generate wp-config.php from env vars (DB credentials)
3. Sync wp-content from DO Spaces bucket
4. Detect table prefix from database
5. Set permissions
6. Start web server
3. WordPress Serves Requests
WordPress core (fresh) → wp-config (env-generated) → wp-content (CDN-synced)
Benefits
Security
- ✅ Fresh WordPress core on every deploy (auto security updates)
- ✅ No credentials in GitHub repo
- ✅ Content CDN-backed (fast, distributed)
Efficiency
- ✅ Tiny Git repos (~100KB instead of 1GB)
- ✅ Fast deployments (no large file transfers)
- ✅ Shared WordPress core across deploys
Maintenance
- ✅ Update WordPress by redeploying (pulls latest)
- ✅ Content updates independent of code deploys
- ✅ Easy rollback (redeploy previous commit)
Content Management
Initial Setup: Upload wp-content to Bucket
# From existing WordPress installation
./sync-content-to-bucket.sh upload
# This uploads:
# - wp-content/themes/
# - wp-content/plugins/
# - wp-content/uploads/
# To: s3://wordpress-{sitename}-content/wp-content/
Update Content (Add plugins, themes, media)
# Option 1: Upload directly to bucket
rclone copy /local/wp-content/plugins/new-plugin \
do-spaces:wordpress-sitename-content/wp-content/plugins/new-plugin
# Option 2: Update via WordPress admin, then sync
# After making changes in WordPress admin:
./sync-content-to-bucket.sh sync
Restore Content from Bucket
# Pull latest content
./sync-content-to-bucket.sh download
Environment Variables
Each App Platform app requires:
Database
- DB_HOST - MySQL cluster hostname
- DB_PORT - MySQL port (25060)
- DB_NAME - Database name (e.g., performwritecom_wp)
- DB_USER - Isolated database user
- DB_PASSWORD - User password
DO Spaces (Content CDN)
- BUCKET_NAME - Bucket name (e.g., wordpress-performwritecom-content)
- BUCKET_ENDPOINT - DO Spaces endpoint (e.g., nyc3.digitaloceanspaces.com)
- BUCKET_ACCESS_KEY - Spaces access key
- BUCKET_SECRET_KEY - Spaces secret key
Site Configuration
Migration from Current Setup
For each WordPress site:
1. Upload wp-content to Bucket
# Create bucket
doctl spaces create wordpress-{sitename}-content --region nyc3
# Upload content from Lightsail/existing server
rclone sync /opt/bitnami/wordpress/wp-content/ \
do-spaces:wordpress-{sitename}-content/wp-content/ \
--exclude 'cache/**' \
--exclude 'upgrade/**'
2. Clean GitHub Repo
cd /tmp/wordpress-repos/wordpress-{sitename}
# Remove all WordPress core files
find . -mindepth 1 -maxdepth 1 ! -name '.git' ! -name '.docker' ! -name '.do' ! -name 'README.md' -exec rm -rf {} +
# Copy deployment templates
cp -r ~/Documents/IBG_HUB/rmm-psa-devops/wordpress-templates/.docker .
cp -r ~/Documents/IBG_HUB/rmm-psa-devops/wordpress-templates/.do .
cp ~/Documents/IBG_HUB/rmm-psa-devops/wordpress-templates/.gitignore .
cp ~/Documents/IBG_HUB/rmm-psa-devops/wordpress-templates/sync-content-to-bucket.sh .
# Commit
git add .
git commit -m "Migrate to egg architecture - minimal deployment files only"
git push origin main
3. Update App Platform Spec
# Add bucket env vars to app
doctl apps update {app-id} --spec .do/app.yaml
4. Redeploy
# App Platform detects change and redeploys
# init.sh downloads fresh WordPress, syncs content from bucket
Example: performwritecom
Before (Current)
wordpress-performwritecom/ (1.2 GB)
├── wp-admin/ (19 MB)
├── wp-includes/ (45 MB)
├── wp-content/ (1.1 GB)
├── wp-config.php
└── ... (hundreds of core files)
After (Egg Architecture)
wordpress-performwritecom/ (150 KB)
├── .docker/
│ ├── init.sh
│ ├── create-wp-config-from-env.php
│ └── Dockerfile
├── .do/
│ └── app.yaml
├── README.md
├── .gitignore
└── sync-content-to-bucket.sh
wp-content/ → s3://wordpress-performwritecom-content/wp-content/ (1.1 GB on CDN)
Updating WordPress
Core Updates
# Automatic: Just redeploy the app
# init.sh will download latest WordPress core
doctl apps create-deployment {app-id}
Plugin/Theme Updates
# Option 1: Via WordPress admin UI (changes saved to bucket)
# Option 2: Update locally and sync to bucket
rclone sync /local/wp-content/plugins/ \
do-spaces:wordpress-{sitename}-content/wp-content/plugins/
Database Schema Updates
WordPress handles automatically on first load after core update.
Rollback Strategy
Rollback Deployment
# Redeploy previous Git commit
git revert HEAD
git push origin main
# App Platform auto-deploys previous version
Rollback Content
# DO Spaces supports versioning
# Enable versioning on buckets:
doctl spaces create wordpress-{sitename}-content --region nyc3 --versioning enabled
# Restore previous version of object
aws s3api list-object-versions --bucket wordpress-{sitename}-content
aws s3api restore-object --bucket ... --key ... --version-id ...
Cost Comparison
Current Approach (Full WordPress in Git)
- GitHub storage: ~1.5 GB × 16 sites = 24 GB
- Deployment time: ~5-10 minutes (large transfers)
- Bandwidth: High (download entire site on each deploy)
Egg Architecture
- GitHub storage: ~150 KB × 16 sites = 2.4 MB (1000× reduction)
- Deployment time: ~1-2 minutes (small scripts)
- Bandwidth: Minimal (WordPress downloads once, content cached on CDN)
- DO Spaces: ~$5/month for 250 GB + transfer (shared across all sites)
Security Considerations
Bucket Access
- Create separate access keys per site
- Use read-only keys for production deployments
- Restrict bucket policies to specific IPs/services
Content Validation
- Scan uploads for malware before syncing to bucket
- Use WordPress security plugins (Wordfence, Sucuri)
- Monitor bucket for unauthorized changes
Credentials
- Never commit credentials to Git
- Use App Platform environment variables
- Rotate keys regularly
Monitoring
Deployment Health
# Check if WordPress core downloaded successfully
doctl apps logs {app-id} | grep "WordPress core downloaded"
# Verify wp-content sync
doctl apps logs {app-id} | grep "wp-content synced"
# Test site
curl -I https://wordpress-{sitename}-{hash}.ondigitalocean.app
Bucket Usage
# Check bucket size
doctl spaces list-objects wordpress-{sitename}-content --recursive | wc -l
# Monitor bandwidth
doctl monitoring metrics bandwidth --resource-type spaces_bucket --resource-id wordpress-{sitename}-content
Troubleshooting
"WordPress core files not found"
- Check init.sh ran successfully: doctl apps logs {app-id} | grep init.sh
- Verify wget worked: Container needs internet access
- Manual fix: SSH into container and run wget https://wordpress.org/latest.tar.gz
"wp-content not syncing from bucket"
- Verify bucket exists: doctl spaces list
- Check credentials: BUCKET_ACCESS_KEY, BUCKET_SECRET_KEY in env vars
- Test rclone manually: rclone ls do-spaces:wordpress-{sitename}-content
"Database table prefix mismatch"
- init.sh auto-detects prefix from {prefix}_options table
- If detection fails, set manually: TABLE_PREFIX env var
- Check logs: doctl apps logs {app-id} | grep "Table prefix"
Related Documentation
Quick Start: Convert Existing Site
# 1. Upload wp-content to bucket
doctl spaces create wordpress-mysite-content --region nyc3
rclone sync /path/to/wordpress/wp-content/ do-spaces:wordpress-mysite-content/wp-content/
# 2. Clean Git repo
cd wordpress-mysite
find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} +
cp -r ../wordpress-templates/{.docker,.do,.gitignore,sync-content-to-bucket.sh} .
# 3. Commit and push
git add .
git commit -m "Migrate to egg architecture"
git push origin main
# 4. Add bucket env vars to App Platform
# (Use DigitalOcean console or doctl)
# 5. Redeploy
doctl apps create-deployment {app-id}
# Done! Fresh WordPress with content from CDN