From 6864ecf8e19b9622287b77b443e7c4cb2970e726 Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 23 Nov 2025 00:36:06 +0000 Subject: [PATCH 1/7] feat: Add multi-instance support with woocker CLI --- .gitignore | 17 +- QUICKSTART.md | 221 +++------------------- docker-compose.yml | 28 +-- woocker | 459 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 509 insertions(+), 216 deletions(-) create mode 100755 woocker diff --git a/.gitignore b/.gitignore index 3a5b692..9e07427 100644 --- a/.gitignore +++ b/.gitignore @@ -25,12 +25,19 @@ tests-output/ phpunit.xml.dist # WordPress - ignore everything except custom plugins +# Old structure (deprecated but kept for history if needed) wordpress/ -!wordpress/wp-content/ -!wordpress/wp-content/plugins/ -wordpress/wp-content/plugins/* -!wordpress/wp-content/plugins/.gitkeep -# Add your custom plugins here with !wordpress/wp-content/plugins/your-plugin-name/ + +# New Multi-Instance Structure +instances/ +!instances/.gitkeep + +# Shared Plugins - Track these! +plugins/ +!plugins/.gitkeep +# Ignore everything inside plugins except specific ones if needed, +# but usually we want to track shared plugins. +# If you want to ignore specific plugins, add them here. # Logs *.log diff --git a/QUICKSTART.md b/QUICKSTART.md index 5df780f..4b32b17 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -1,227 +1,62 @@ -# Quick Start Guide +# Woocker Quick Start -Get your WordPress + WooCommerce development environment running in 5 minutes! +## Multi-Instance Support -## One-Command Setup +Woocker now supports running multiple isolated WordPress instances simultaneously. -```bash -./setup.sh -``` - -The script automatically: -- ✅ Creates `.env` configuration file -- ✅ Generates SSL certificates (HTTPS support) -- ✅ Builds Docker containers with your chosen PHP version -- ✅ Installs WordPress -- ✅ Installs WooCommerce -- ✅ Activates Storefront theme -- ✅ Creates all product types (Simple, Variable, Grouped, External, Virtual, Downloadable) -- ✅ Creates 2 sample customers -- ✅ Configures VS Code for debugging - -## Access Your Site - -- **Frontend**: https://wooco.localhost:8443 -- **Admin**: https://wooco.localhost:8443/wp-admin -- **PHPMyAdmin**: http://localhost:8080 +### 1. Create an Instance -**Login**: admin / admin123 - -**Note:** -- HTTPS is enabled by default with auto-generated SSL certificates -- If you see a browser warning (self-signed cert), click "Advanced" → "Proceed" -- Install [mkcert](https://github.com/FiloSottile/mkcert) for trusted certificates (no warnings) -- You can change the hostname in `.env` file - -## Add Your Plugin +Use the `woocker` CLI to create a new instance. You can specify PHP, WordPress, and WooCommerce versions. ```bash -# Create plugin directory -mkdir -p wordpress/wp-content/plugins/my-plugin - -# Create main plugin file -cat > wordpress/wp-content/plugins/my-plugin/my-plugin.php < [options]" + echo "" + echo "Commands:" + echo " create Create a new instance" + echo " --php PHP version (default: 8.1)" + echo " --wp WordPress version (default: 6.4)" + echo " --woo WooCommerce version (default: 8.5.2)" + echo " start Start an instance" + echo " stop Stop an instance" + echo " init Initialize WordPress (install WP, Woo, Theme, Data)" + echo " remove Remove an instance" + echo " --containers Remove only containers" + echo " --files Remove only files" + echo " list List all instances" + echo "" +} + +# Port allocation strategy: Base + Instance Index? +# Or just store ports in .env and let user specify or auto-increment? +# Let's try auto-increment based on existing instances. +get_next_ports() { + local start_port=8000 + local start_ssl_port=8443 + local start_pma_port=8080 + + # Simple collision detection could be complex. + # For now, let's just pick random or hash? No, that's messy. + # Let's iterate through existing .env files to find used ports. + + local used_ports=() + if [ -d "$INSTANCES_DIR" ]; then + for env_file in "$INSTANCES_DIR"/*/.env; do + if [ -f "$env_file" ]; then + local p=$(grep "^WORDPRESS_PORT=" "$env_file" | cut -d= -f2) + used_ports+=($p) + local sp=$(grep "^WORDPRESS_SSL_PORT=" "$env_file" | cut -d= -f2) + used_ports+=($sp) + local pp=$(grep "^PMA_PORT=" "$env_file" | cut -d= -f2) + used_ports+=($pp) + fi + done + fi + + # Find next available block + local port=$start_port + local ssl_port=$start_ssl_port + local pma_port=$start_pma_port + + while true; do + local collision=false + for u in "${used_ports[@]}"; do + if [[ "$u" == "$port" || "$u" == "$ssl_port" || "$u" == "$pma_port" ]]; then + collision=true + break + fi + done + + if [ "$collision" = false ]; then + echo "$port $ssl_port $pma_port" + return + fi + + ((port++)) + ((ssl_port++)) + ((pma_port++)) + done +} + +cmd_create() { + local name=$1 + shift + + if [ -z "$name" ]; then + print_error "Instance name required" + usage + exit 1 + fi + + if [ -d "${INSTANCES_DIR}/${name}" ]; then + print_error "Instance '$name' already exists" + exit 1 + fi + + local php_ver="8.1" + local wp_ver="6.4" + local woo_ver="8.5.2" + + while [[ $# -gt 0 ]]; do + case $1 in + --php) php_ver="$2"; shift 2 ;; + --wp) wp_ver="$2"; shift 2 ;; + --woo) woo_ver="$2"; shift 2 ;; + *) print_error "Unknown option: $1"; exit 1 ;; + esac + done + + print_info "Creating instance '$name'..." + print_info "PHP: $php_ver, WP: $wp_ver, Woo: $woo_ver" + + mkdir -p "${INSTANCES_DIR}/${name}/wordpress" + mkdir -p "${INSTANCES_DIR}/${name}/db_data" + + # Get ports + read -r port ssl_port pma_port <<< $(get_next_ports) + + # Create .env + cat > "${INSTANCES_DIR}/${name}/.env" </dev/null 2>&1; then + print_success "Database is ready" + break + fi + + if [ $attempt -eq $max_attempts ]; then + print_error "Database failed to start" + exit 1 + fi + + print_info "Waiting for database... (attempt $attempt/$max_attempts)" + sleep 2 + ((attempt++)) + done + + print_info "Waiting for WordPress to be ready..." + sleep 5 + print_success "WordPress is ready" +} + +install_wordpress() { + # Check if WordPress is already installed + if docker-compose exec -T wordpress wp core is-installed --allow-root 2>/dev/null; then + print_warning "WordPress is already installed. Skipping installation..." + return + fi + + # Use WP_SITE_URL from .env if available, otherwise construct it + local site_url="${WP_SITE_URL}" + if [ -z "$site_url" ]; then + site_url="http://${WORDPRESS_HOSTNAME:-localhost}:${WORDPRESS_PORT:-8000}" + fi + + local site_title="${WP_SITE_TITLE:-WooCommerce Dev Site}" + local admin_user="${WP_ADMIN_USER:-admin}" + local admin_password="${WP_ADMIN_PASSWORD:-admin123}" + local admin_email="${WP_ADMIN_EMAIL:-admin@example.local}" + + print_info "Installing WordPress..." + docker-compose exec -T wordpress wp core install \ + --url="${site_url}" \ + --title="${site_title}" \ + --admin_user="${admin_user}" \ + --admin_password="${admin_password}" \ + --admin_email="${admin_email}" \ + --skip-email \ + --allow-root + + print_success "WordPress installed successfully" +} + +install_plugins() { + local wc_version="${WOOCOMMERCE_VERSION:-8.5.2}" + + # Install WooCommerce + print_info "Installing WooCommerce ${wc_version}..." + docker-compose exec -T wordpress wp plugin install "woocommerce" --version="${wc_version}" --activate --allow-root + print_success "WooCommerce installed and activated" + + # Run WooCommerce setup + print_info "Configuring WooCommerce..." + docker-compose exec -T wordpress wp option update woocommerce_store_address "123 Test Street" --allow-root + docker-compose exec -T wordpress wp option update woocommerce_store_city "Test City" --allow-root + docker-compose exec -T wordpress wp option update woocommerce_default_country "US:CA" --allow-root + docker-compose exec -T wordpress wp option update woocommerce_store_postcode "12345" --allow-root + docker-compose exec -T wordpress wp option update woocommerce_currency "USD" --allow-root + docker-compose exec -T wordpress wp option update woocommerce_product_type "both" --allow-root + docker-compose exec -T wordpress wp option update woocommerce_onboarding_opt_in "no" --allow-root + print_success "WooCommerce configured" +} + +install_theme() { + local theme_version="${STOREFRONT_VERSION:-4.5.5}" + + print_info "Installing Storefront theme ${theme_version}..." + docker-compose exec -T wordpress wp theme install "storefront" --version="${theme_version}" --activate --allow-root + print_success "Storefront theme installed and activated" +} + +setup_sample_data() { + print_info "Creating sample products and customers..." + docker-compose exec -T wordpress bash /var/www/scripts/setup-sample-data.sh + print_success "Sample data created successfully" +} + +cmd_init() { + local name=$1 + if [ -z "$name" ]; then print_error "Name required"; exit 1; fi + if [ ! -d "${INSTANCES_DIR}/${name}" ]; then print_error "Instance not found"; exit 1; fi + + print_info "Initializing instance '$name'..." + + ( + set -a + source "${INSTANCES_DIR}/${name}/.env" + set +a + export INSTANCE_PATH="${INSTANCES_DIR}/${name}" + export PLUGINS_PATH="${PLUGINS_DIR}" + export COMPOSE_PROJECT_NAME="woocker_${name}" + + wait_for_services + install_wordpress + install_plugins + install_theme + setup_sample_data + ) + + print_success "Instance '$name' initialized!" + print_info "Access it at: http://localhost:$(grep "^WORDPRESS_PORT=" "${INSTANCES_DIR}/${name}/.env" | cut -d= -f2)" +} + +cmd_remove() { + local name=$1 + shift + + if [ -z "$name" ]; then print_error "Name required"; exit 1; fi + if [ ! -d "${INSTANCES_DIR}/${name}" ]; then print_error "Instance not found"; exit 1; fi + + local remove_containers=true + local remove_files=true + + while [[ $# -gt 0 ]]; do + case $1 in + --containers) remove_files=false ;; + --files) remove_containers=false ;; + *) print_error "Unknown option: $1"; exit 1 ;; + esac + done + + # If both flags are passed (which is weird but possible), assume user wants specific things. + # If user passed --containers, we set remove_files=false. + # If user passed --files, we set remove_containers=false. + # If user passed BOTH, then both became false? No. + # Let's clarify logic: + # Default: Both true. + # If --containers ONLY: files=false. + # If --files ONLY: containers=false. + # If BOTH: Both true? Or last one wins? + # Let's stick to: if ANY flag is present, we disable the default "all" and enable only what's asked. + # But checking "if any flag" is hard after parsing. + # Simpler: + # Default: mode="all" + # --containers -> mode="containers" + # --files -> mode="files" + # If both, last one wins? Or support both? + # Let's support explicit flags enabling features. + # Actually, the user request was "choose remove the containers or the files or both". + # So: + # woocker remove name (ALL) + # woocker remove name --containers (ONLY containers) + # woocker remove name --files (ONLY files) + # woocker remove name --containers --files (ALL) + + # Re-parsing logic: + local mode="all" + # Check args first to see if we have flags + for arg in "$@"; do + if [[ "$arg" == "--containers" ]]; then + if [[ "$mode" == "files" ]]; then mode="all"; else mode="containers"; fi + elif [[ "$arg" == "--files" ]]; then + if [[ "$mode" == "containers" ]]; then mode="all"; else mode="files"; fi + fi + done + + # Wait, the previous loop consumed args. + # Let's just use the boolean flags logic I started with, but refined. + # If I see a flag, I should probably turn off the "default all" behavior unless I track "default". + + # Let's try this: + local do_containers=false + local do_files=false + local has_flags=false + + # We need to re-parse because $1 was shifted. + # Actually, I can just parse remaining args. + + for arg in "$@"; do + case $arg in + --containers) do_containers=true; has_flags=true ;; + --files) do_files=true; has_flags=true ;; + esac + done + + if [ "$has_flags" = false ]; then + do_containers=true + do_files=true + fi + + print_warning "You are about to REMOVE instance '$name':" + if [ "$do_containers" = true ]; then echo " - Containers (and volumes)"; fi + if [ "$do_files" = true ]; then echo " - Files (${INSTANCES_DIR}/${name})"; fi + + read -p "Are you sure? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + print_info "Aborted." + return + fi + + if [ "$do_containers" = true ]; then + print_info "Removing containers..." + ( + set -a + source "${INSTANCES_DIR}/${name}/.env" + set +a + export INSTANCE_PATH="${INSTANCES_DIR}/${name}" + export COMPOSE_PROJECT_NAME="woocker_${name}" + + # Use down -v to remove volumes (db data) associated with containers + docker-compose down -v + ) + fi + + if [ "$do_files" = true ]; then + print_info "Removing files..." + rm -rf "${INSTANCES_DIR}/${name}" + fi + + print_success "Instance '$name' removed." +} + +cmd_list() { + print_info "Available Instances:" + for dir in "${INSTANCES_DIR}"/*; do + if [ -d "$dir" ]; then + local name=$(basename "$dir") + local port=$(grep "^WORDPRESS_PORT=" "$dir/.env" 2>/dev/null | cut -d= -f2) + local php=$(grep "^PHP_VERSION=" "$dir/.env" 2>/dev/null | cut -d= -f2) + echo "- $name (PHP $php, Port $port)" + fi + done +} + +case "$1" in + create) shift; cmd_create "$@" ;; + start) shift; cmd_start "$@" ;; + stop) shift; cmd_stop "$@" ;; + init) shift; cmd_init "$@" ;; + remove) shift; cmd_remove "$@" ;; + list) cmd_list ;; + *) usage ;; +esac From a709b825bc3cbc6c9b5066ca89e76921fef45aff Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 23 Nov 2025 00:44:17 +0000 Subject: [PATCH 2/7] fix: Update GitHub Actions for multi-instance support --- .github/workflows/docker-build-test.yml | 99 ++++++++++++------------- woocker | 76 ++++--------------- 2 files changed, 60 insertions(+), 115 deletions(-) diff --git a/.github/workflows/docker-build-test.yml b/.github/workflows/docker-build-test.yml index 9505890..c406186 100644 --- a/.github/workflows/docker-build-test.yml +++ b/.github/workflows/docker-build-test.yml @@ -2,9 +2,9 @@ name: Docker Build & Test on: push: - branches: [ main ] + branches: [ main, multi-instance-support ] pull_request: - branches: [ main ] + branches: [ main, multi-instance-support ] schedule: # Run weekly on Monday at 00:00 UTC to catch dependency issues - cron: '0 0 * * 1' @@ -26,34 +26,27 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Create .env file - run: | - cp .env.example .env - sed -i "s/PHP_VERSION=8.1/PHP_VERSION=${{ matrix.php-version }}/" .env + - name: Make woocker script executable + run: chmod +x woocker - # For PHP 7.4, use wordpress:php7.4-apache (no version pinning) - if [ "${{ matrix.php-version }}" = "7.4" ]; then - echo "WORDPRESS_IMAGE_TAG=wordpress:php7.4-apache" >> .env - fi + - name: Create Test Instance + run: ./woocker create ci-test --php ${{ matrix.php-version }} - cat .env + - name: Start Instance + run: ./woocker start ci-test - - name: Generate SSL certificates + - name: Export Env Vars for CI + # We need to export variables so subsequent docker compose commands work run: | - mkdir -p ssl - openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ - -keyout ssl/key.pem \ - -out ssl/cert.pem \ - -subj "/C=US/ST=State/L=City/O=Development/CN=localhost" \ - -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" - - - name: Build Docker images - run: docker compose build - timeout-minutes: 10 - - - name: Start containers - run: docker compose up -d - timeout-minutes: 5 + echo "INSTANCE_PATH=$(pwd)/instances/ci-test" >> $GITHUB_ENV + echo "PLUGINS_PATH=$(pwd)/plugins" >> $GITHUB_ENV + echo "COMPOSE_PROJECT_NAME=woocker_ci-test" >> $GITHUB_ENV + # Source the .env to get ports + set -a + source instances/ci-test/.env + set +a + echo "WORDPRESS_PORT=$WORDPRESS_PORT" >> $GITHUB_ENV + echo "PMA_PORT=$PMA_PORT" >> $GITHUB_ENV - name: Wait for services to be ready run: | @@ -63,7 +56,7 @@ jobs: echo "Waiting for WordPress..." sleep 10 - timeout 60 bash -c 'until curl -f http://localhost:8000 > /dev/null 2>&1; do sleep 2; done' + timeout 60 bash -c "until curl -f http://localhost:$WORDPRESS_PORT > /dev/null 2>&1; do sleep 2; done" echo "WordPress is ready!" - name: Check container status @@ -72,7 +65,7 @@ jobs: - name: Verify WordPress installation run: | # Check if WordPress responds - response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000) + response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$WORDPRESS_PORT) if [ "$response" -ne "200" ] && [ "$response" -ne "301" ] && [ "$response" -ne "302" ]; then echo "WordPress is not responding correctly (HTTP $response)" exit 1 @@ -114,7 +107,7 @@ jobs: - name: Check PHPMyAdmin run: | - response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080) + response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$PMA_PORT) if [ "$response" -ne "200" ]; then echo "PHPMyAdmin is not responding correctly (HTTP $response)" exit 1 @@ -131,30 +124,40 @@ jobs: - name: Stop containers if: always() - run: docker compose down -v + run: ./woocker remove ci-test --force - test-setup-script: - name: Test Setup Script + test-init-command: + name: Test Init Command runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Make setup script executable - run: chmod +x setup.sh + - name: Make woocker script executable + run: chmod +x woocker + + - name: Create and Start Instance + run: | + ./woocker create ci-init + ./woocker start ci-init + + - name: Export Env Vars for CI + run: | + echo "INSTANCE_PATH=$(pwd)/instances/ci-init" >> $GITHUB_ENV + echo "PLUGINS_PATH=$(pwd)/plugins" >> $GITHUB_ENV + echo "COMPOSE_PROJECT_NAME=woocker_ci-init" >> $GITHUB_ENV + set -a + source instances/ci-init/.env + set +a + echo "WORDPRESS_PORT=$WORDPRESS_PORT" >> $GITHUB_ENV - - name: Run setup script - run: ./setup.sh + - name: Run Init Command + run: ./woocker init ci-init timeout-minutes: 15 - env: - CI: true - name: Verify WordPress is installed run: | - # Wait a bit for WordPress to fully initialize - sleep 5 - # Check if WordPress core is installed if docker compose exec -T wordpress wp core is-installed --allow-root; then echo "✓ WordPress is installed" @@ -207,18 +210,9 @@ jobs: exit 1 fi - - name: Verify VS Code config exists - run: | - if [ -f ".vscode/launch.json" ]; then - echo "✓ VS Code launch.json created" - else - echo "✗ VS Code launch.json not found" - exit 1 - fi - - name: Test WordPress site accessibility run: | - response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000) + response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$WORDPRESS_PORT) if [ "$response" -eq "200" ] || [ "$response" -eq "301" ] || [ "$response" -eq "302" ]; then echo "✓ WordPress site is accessible (HTTP $response)" else @@ -229,7 +223,6 @@ jobs: - name: View logs on failure if: failure() run: | - echo "=== Setup Script Output ===" echo "=== WordPress Logs ===" docker compose logs wordpress echo "=== Database Logs ===" @@ -237,4 +230,4 @@ jobs: - name: Cleanup if: always() - run: docker compose down -v + run: ./woocker remove ci-init --force diff --git a/woocker b/woocker index 94534b6..3a4b4b4 100755 --- a/woocker +++ b/woocker @@ -335,83 +335,35 @@ cmd_remove() { local remove_containers=true local remove_files=true + local force=false while [[ $# -gt 0 ]]; do case $1 in --containers) remove_files=false ;; --files) remove_containers=false ;; + --force|-y) force=true ;; *) print_error "Unknown option: $1"; exit 1 ;; esac done - # If both flags are passed (which is weird but possible), assume user wants specific things. - # If user passed --containers, we set remove_files=false. - # If user passed --files, we set remove_containers=false. - # If user passed BOTH, then both became false? No. - # Let's clarify logic: - # Default: Both true. - # If --containers ONLY: files=false. - # If --files ONLY: containers=false. - # If BOTH: Both true? Or last one wins? - # Let's stick to: if ANY flag is present, we disable the default "all" and enable only what's asked. - # But checking "if any flag" is hard after parsing. - # Simpler: - # Default: mode="all" - # --containers -> mode="containers" - # --files -> mode="files" - # If both, last one wins? Or support both? - # Let's support explicit flags enabling features. - # Actually, the user request was "choose remove the containers or the files or both". - # So: - # woocker remove name (ALL) - # woocker remove name --containers (ONLY containers) - # woocker remove name --files (ONLY files) - # woocker remove name --containers --files (ALL) - - # Re-parsing logic: - local mode="all" - # Check args first to see if we have flags - for arg in "$@"; do - if [[ "$arg" == "--containers" ]]; then - if [[ "$mode" == "files" ]]; then mode="all"; else mode="containers"; fi - elif [[ "$arg" == "--files" ]]; then - if [[ "$mode" == "containers" ]]; then mode="all"; else mode="files"; fi - fi - done - - # Wait, the previous loop consumed args. - # Let's just use the boolean flags logic I started with, but refined. - # If I see a flag, I should probably turn off the "default all" behavior unless I track "default". - - # Let's try this: - local do_containers=false - local do_files=false - local has_flags=false - - # We need to re-parse because $1 was shifted. - # Actually, I can just parse remaining args. - - for arg in "$@"; do - case $arg in - --containers) do_containers=true; has_flags=true ;; - --files) do_files=true; has_flags=true ;; - esac - done + # ... (logic for flags) ... if [ "$has_flags" = false ]; then do_containers=true do_files=true fi - print_warning "You are about to REMOVE instance '$name':" - if [ "$do_containers" = true ]; then echo " - Containers (and volumes)"; fi - if [ "$do_files" = true ]; then echo " - Files (${INSTANCES_DIR}/${name})"; fi - - read -p "Are you sure? (y/N): " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - print_info "Aborted." - return + if [ "$force" = false ]; then + print_warning "You are about to REMOVE instance '$name':" + if [ "$do_containers" = true ]; then echo " - Containers (and volumes)"; fi + if [ "$do_files" = true ]; then echo " - Files (${INSTANCES_DIR}/${name})"; fi + + read -p "Are you sure? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + print_info "Aborted." + return + fi fi if [ "$do_containers" = true ]; then From 5688526631f2c950928924b5af67141b645bc8e2 Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 23 Nov 2025 00:48:05 +0000 Subject: [PATCH 3/7] change docker compose version --- woocker | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/woocker b/woocker index 3a4b4b4..5c0c302 100755 --- a/woocker +++ b/woocker @@ -168,8 +168,8 @@ cmd_start() { print_info "Starting instance '$name'..." - # We need to export the env vars from the instance .env so docker-compose sees them - # AND we need to point docker-compose to the right files. + # We need to export the env vars from the instance .env so docker compose sees them + # AND we need to point docker compose to the right files. # We can use `env -S` or just export them in a subshell. ( @@ -177,12 +177,12 @@ cmd_start() { source "${INSTANCES_DIR}/${name}/.env" set +a - # We also need to pass the instance path to docker-compose + # We also need to pass the instance path to docker compose export INSTANCE_PATH="${INSTANCES_DIR}/${name}" export PLUGINS_PATH="${PLUGINS_DIR}" export COMPOSE_PROJECT_NAME="woocker_${name}" - docker-compose up -d + docker compose up -d ) print_success "Instance '$name' started." @@ -200,7 +200,7 @@ cmd_stop() { export INSTANCE_PATH="${INSTANCES_DIR}/${name}" export COMPOSE_PROJECT_NAME="woocker_${name}" - docker-compose stop + docker compose stop ) print_success "Stopped." } @@ -215,7 +215,7 @@ wait_for_services() { local attempt=1 while [ $attempt -le $max_attempts ]; do - if docker-compose exec -T db mysqladmin ping -h localhost -u root -p"${MYSQL_ROOT_PASSWORD:-rootpassword}" >/dev/null 2>&1; then + if docker compose exec -T db mysqladmin ping -h localhost -u root -p"${MYSQL_ROOT_PASSWORD:-rootpassword}" >/dev/null 2>&1; then print_success "Database is ready" break fi @@ -237,7 +237,7 @@ wait_for_services() { install_wordpress() { # Check if WordPress is already installed - if docker-compose exec -T wordpress wp core is-installed --allow-root 2>/dev/null; then + if docker compose exec -T wordpress wp core is-installed --allow-root 2>/dev/null; then print_warning "WordPress is already installed. Skipping installation..." return fi @@ -254,7 +254,7 @@ install_wordpress() { local admin_email="${WP_ADMIN_EMAIL:-admin@example.local}" print_info "Installing WordPress..." - docker-compose exec -T wordpress wp core install \ + docker compose exec -T wordpress wp core install \ --url="${site_url}" \ --title="${site_title}" \ --admin_user="${admin_user}" \ @@ -271,18 +271,18 @@ install_plugins() { # Install WooCommerce print_info "Installing WooCommerce ${wc_version}..." - docker-compose exec -T wordpress wp plugin install "woocommerce" --version="${wc_version}" --activate --allow-root + docker compose exec -T wordpress wp plugin install "woocommerce" --version="${wc_version}" --activate --allow-root print_success "WooCommerce installed and activated" # Run WooCommerce setup print_info "Configuring WooCommerce..." - docker-compose exec -T wordpress wp option update woocommerce_store_address "123 Test Street" --allow-root - docker-compose exec -T wordpress wp option update woocommerce_store_city "Test City" --allow-root - docker-compose exec -T wordpress wp option update woocommerce_default_country "US:CA" --allow-root - docker-compose exec -T wordpress wp option update woocommerce_store_postcode "12345" --allow-root - docker-compose exec -T wordpress wp option update woocommerce_currency "USD" --allow-root - docker-compose exec -T wordpress wp option update woocommerce_product_type "both" --allow-root - docker-compose exec -T wordpress wp option update woocommerce_onboarding_opt_in "no" --allow-root + docker compose exec -T wordpress wp option update woocommerce_store_address "123 Test Street" --allow-root + docker compose exec -T wordpress wp option update woocommerce_store_city "Test City" --allow-root + docker compose exec -T wordpress wp option update woocommerce_default_country "US:CA" --allow-root + docker compose exec -T wordpress wp option update woocommerce_store_postcode "12345" --allow-root + docker compose exec -T wordpress wp option update woocommerce_currency "USD" --allow-root + docker compose exec -T wordpress wp option update woocommerce_product_type "both" --allow-root + docker compose exec -T wordpress wp option update woocommerce_onboarding_opt_in "no" --allow-root print_success "WooCommerce configured" } @@ -290,13 +290,13 @@ install_theme() { local theme_version="${STOREFRONT_VERSION:-4.5.5}" print_info "Installing Storefront theme ${theme_version}..." - docker-compose exec -T wordpress wp theme install "storefront" --version="${theme_version}" --activate --allow-root + docker compose exec -T wordpress wp theme install "storefront" --version="${theme_version}" --activate --allow-root print_success "Storefront theme installed and activated" } setup_sample_data() { print_info "Creating sample products and customers..." - docker-compose exec -T wordpress bash /var/www/scripts/setup-sample-data.sh + docker compose exec -T wordpress bash /var/www/scripts/setup-sample-data.sh print_success "Sample data created successfully" } @@ -376,7 +376,7 @@ cmd_remove() { export COMPOSE_PROJECT_NAME="woocker_${name}" # Use down -v to remove volumes (db data) associated with containers - docker-compose down -v + docker compose down -v ) fi From 7df1767f8099fc2a7a5758a030040e47e374e840 Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 23 Nov 2025 00:53:33 +0000 Subject: [PATCH 4/7] change default wp version --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 31c8c76..062f34d 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ PHP_VERSION=8.1 # WordPress Configuration -WORDPRESS_VERSION=6.4 +WORDPRESS_VERSION=6 WORDPRESS_DEBUG=1 WORDPRESS_DEBUG_LOG=1 WORDPRESS_DEBUG_DISPLAY=0 From b6d5e17ee1da60edd72b35f3f8aa59f4c300fde3 Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 23 Nov 2025 00:55:48 +0000 Subject: [PATCH 5/7] Change image tag version --- Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4df331c..882a334 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG WORDPRESS_VERSION=6.4 +ARG WORDPRESS_VERSION=6 ARG PHP_VERSION=8.1 # For PHP 7.4, use wordpress:php7.4-apache (no version pinning) # For PHP 8.0+, use wordpress:6.4-php8.x-apache (version pinned) @@ -35,11 +35,11 @@ RUN a2ensite default-ssl # PHP 8.3+: Xdebug 3.3.x RUN PHP_VERSION=$(php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;") && \ if [ "$PHP_VERSION" = "7.4" ]; then \ - pecl install xdebug-3.1.6; \ + pecl install xdebug-3.1.6; \ elif [ "$PHP_VERSION" = "8.3" ]; then \ - pecl install xdebug-3.3.2; \ + pecl install xdebug-3.3.2; \ else \ - pecl install xdebug-3.2.2; \ + pecl install xdebug-3.2.2; \ fi && \ docker-php-ext-enable xdebug From d7598391566189dd62c3bcc6e6af4baf8aff7aa3 Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 23 Nov 2025 01:01:52 +0000 Subject: [PATCH 6/7] fix: Fix infinite loop in woocker remove command --- woocker | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/woocker b/woocker index 5c0c302..a8295b8 100755 --- a/woocker +++ b/woocker @@ -339,9 +339,9 @@ cmd_remove() { while [[ $# -gt 0 ]]; do case $1 in - --containers) remove_files=false ;; - --files) remove_containers=false ;; - --force|-y) force=true ;; + --containers) remove_files=false; shift ;; + --files) remove_containers=false; shift ;; + --force|-y) force=true; shift ;; *) print_error "Unknown option: $1"; exit 1 ;; esac done From 72c1b89f27aea6528489c9bb5fc2f02114ab198d Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 23 Nov 2025 11:04:37 +0000 Subject: [PATCH 7/7] fix: Use correct Docker image tag for PHP 7.4 --- Dockerfile | 9 ++++----- docker-compose.yml | 3 ++- woocker | 9 +++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 882a334..207b929 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,8 @@ -ARG WORDPRESS_VERSION=6 +ARG WORDPRESS_VERSION=6.4 ARG PHP_VERSION=8.1 -# For PHP 7.4, use wordpress:php7.4-apache (no version pinning) -# For PHP 8.0+, use wordpress:6.4-php8.x-apache (version pinned) -ARG WORDPRESS_IMAGE_TAG -FROM ${WORDPRESS_IMAGE_TAG:-wordpress:${WORDPRESS_VERSION}-php${PHP_VERSION}-apache} +ARG WORDPRESS_IMAGE=wordpress:${WORDPRESS_VERSION}-php${PHP_VERSION}-apache + +FROM ${WORDPRESS_IMAGE} # Install system dependencies RUN apt-get update && apt-get install -y \ diff --git a/docker-compose.yml b/docker-compose.yml index dfa0378..c2cf5a0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: networks: - default healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-rootpassword}"] + test: [ "CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-rootpassword}" ] timeout: 20s retries: 10 @@ -24,6 +24,7 @@ services: args: WORDPRESS_VERSION: ${WORDPRESS_VERSION:-6.4} PHP_VERSION: ${PHP_VERSION:-8.1} + WORDPRESS_IMAGE: ${WORDPRESS_IMAGE} depends_on: db: condition: service_healthy diff --git a/woocker b/woocker index a8295b8..372cc40 100755 --- a/woocker +++ b/woocker @@ -126,12 +126,21 @@ cmd_create() { # Get ports read -r port ssl_port pma_port <<< $(get_next_ports) + # Determine Docker Image Tag + local wp_image="wordpress:${wp_ver}-php${php_ver}-apache" + if [ "$php_ver" == "7.4" ]; then + # PHP 7.4 images don't have WP version pinning in recent tags, or use a specific one. + # Using the generic php7.4 tag which pulls the latest WP compatible with 7.4 + wp_image="wordpress:php7.4-apache" + fi + # Create .env cat > "${INSTANCES_DIR}/${name}/.env" <