基于docker的wordpress搭建

1. 创建项目目录和必要文件

# 创建主目录
mkdir -p /opt/wordpress
cd /opt/wordpress

# 创建所有需要的子目录
mkdir -p {db_data,wordpress_data,nginx-conf,certs,backups,redis_data}

# 创建环境变量文件(密码以混合形式加密存储)
cat > .env << 'EOF'
MYSQL_ROOT_PASSWORD=ENCRYPTED_PASSWORD_$(echo -n "guojiulin.cn" | base64)_END
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=ENCRYPTED_PASSWORD_$(echo -n "guojiulin.cn" | base64)_END
WORDPRESS_DB_HOST=db:3306
WORDPRESS_DB_USER=wordpress
WORDPRESS_DB_PASSWORD=ENCRYPTED_PASSWORD_$(echo -n "guojiulin.cn" | base64)_END
WORDPRESS_DB_NAME=wordpress
REDIS_PASSWORD=ENCRYPTED_PASSWORD_$(echo -n "guojiulin.cn" | base64)_END
EOF

# 创建密码解密脚本
cat > decrypt_password.sh << 'EOF'
#!/bin/bash
echo "$1" | awk -F'ENCRYPTED_PASSWORD_|_END' '{print $2}' | base64 -d
EOF
chmod +x decrypt_password.sh

2. 创建优化版 Nginx 配置文件

cat > nginx-conf/nginx.conf << 'EOF'
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 4096;
    multi_accept on;
    use epoll;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main buffer=32k flush=5s;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    keepalive_requests 1000;
    types_hash_max_size 2048;
    server_tokens off;
    client_max_body_size 64M;

    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_min_length 256;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    open_file_cache max=200000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=WORDPRESS:100m inactive=60m;
    fastcgi_cache_key "$scheme$request_method$host$request_uri";
    fastcgi_cache_use_stale error timeout invalid_header http_500;
    fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

    upstream wordpress {
        server wordpress:9000;
        keepalive 32;
    }

    server {
        listen 80;
        server_name guijiulin.cn www.guijiulin.cn;
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name guijiulin.cn www.guijiulin.cn;

        ssl_certificate /etc/nginx/ssl/guijiulin.cn.crt;
        ssl_certificate_key /etc/nginx/ssl/guijiulin.cn.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
        ssl_prefer_server_ciphers off;
        ssl_session_cache shared:SSL:50m;
        ssl_session_timeout 1d;
        ssl_session_tickets off;

        add_header Strict-Transport-Security "max-age=63072000" always;
        add_header X-Frame-Options SAMEORIGIN always;
        add_header X-Content-Type-Options nosniff always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;

        root /var/www/html;
        index index.php;

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

        location ~ \.php$ {
            fastcgi_pass wordpress;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
            fastcgi_param HTTPS on;
            fastcgi_buffers 256 16k;
            fastcgi_buffer_size 128k;
            fastcgi_connect_timeout 5s;
            fastcgi_send_timeout 600s;
            fastcgi_read_timeout 600s;
            fastcgi_busy_buffers_size 256k;
            fastcgi_temp_file_write_size 256k;
            fastcgi_intercept_errors on;
            
            fastcgi_cache WORDPRESS;
            fastcgi_cache_valid 200 301 302 60m;
            fastcgi_cache_valid 404 1m;
            fastcgi_cache_bypass $skip_cache;
            fastcgi_no_cache $skip_cache;
            add_header X-Cache $upstream_cache_status;
        }

        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
            expires 365d;
            add_header Cache-Control "public, immutable";
            access_log off;
        }

        location ~ /\.ht {
            deny all;
        }

        location = /xmlrpc.php {
            deny all;
            access_log off;
            log_not_found off;
        }

        location ~* wp-config.php {
            deny all;
        }

        location ~* readme.html {
            deny all;
        }

        location ~* license.txt {
            deny all;
        }
    }
}
EOF

3. 创建 PHP 配置文件

cat > uploads.ini << 'EOF'
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 300
max_input_time = 300
max_input_vars = 5000
realpath_cache_size = 4096K
realpath_cache_ttl = 600
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
opcache.fast_shutdown=1
EOF

4. 创建优化的 MySQL 配置文件

cat > mysql.cnf << 'EOF'
[mysqld]
max_allowed_packet=256M
innodb_buffer_pool_size=2G
innodb_log_file_size=512M
innodb_file_per_table=1
innodb_flush_log_at_trx_commit=2
innodb_flush_method=O_DIRECT
innodb_buffer_pool_instances=8
innodb_read_io_threads=64
innodb_write_io_threads=64
innodb_io_capacity=2000
innodb_io_capacity_max=4000
query_cache_type=1
query_cache_size=128M
query_cache_limit=2M
thread_cache_size=32
table_open_cache=2000
max_connections=200
slow_query_log=1
slow_query_log_file=/var/lib/mysql/slow.log
long_query_time=2
log_queries_not_using_indexes=1
performance_schema=ON
EOF

5. 创建 Redis 配置文件

cat > redis.conf << 'EOF'
maxmemory 1gb
maxmemory-policy allkeys-lru
appendonly yes
appendfsync everysec
save 900 1
save 300 10
save 60 10000
activerehashing yes
hz 10
tcp-keepalive 300
EOF

6. 创建最终的 docker-compose.yml

cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  db:
    image: mariadb:10.6
    container_name: wordpress_db
    restart: always
    env_file:
      - .env
    command: 
      - --max-allowed-packet=256M
      - --innodb-buffer-pool-size=2G
      - --innodb-log-file-size=512M
      - --innodb-file-per-table=1
      - --innodb-flush-log-at-trx-commit=2
      - --innodb-flush-method=O_DIRECT
      - --performance-schema=ON
    volumes:
      - ./db_data:/var/lib/mysql
      - ./backups:/backups
      - ./mysql.cnf:/etc/mysql/conf.d/mysql.cnf:ro
    networks:
      - wp-network
    deploy:
      resources:
        limits:
          memory: 4G
          cpus: '2'
        reservations:
          memory: 1G
          cpus: '0.5'
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10

  redis:
    image: redis:alpine
    container_name: wordpress_redis
    restart: always
    command: redis-server /usr/local/etc/redis/redis.conf
    volumes:
      - ./redis_data:/data
      - ./redis.conf:/usr/local/etc/redis/redis.conf:ro
    networks:
      - wp-network
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 256M
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      timeout: 10s
      retries: 5

  wordpress:
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    image: wordpress:6.4-php8.2-fpm-alpine
    container_name: wordpress_app
    restart: always
    env_file:
      - .env
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ENCRYPTED_PASSWORD_$(echo -n "guojiulin.cn" | base64)_END
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_CONFIG_EXTRA: |
        define('WP_MEMORY_LIMIT', '256M');
        define('WP_MAX_MEMORY_LIMIT', '512M');
        define('FORCE_SSL_ADMIN', true);
        define('FORCE_SSL_LOGIN', true);
        $_SERVER['HTTPS'] = 'on';
        define('WP_DEBUG', false);
        define('WP_DEBUG_LOG', false);
        define('WP_DEBUG_DISPLAY', false);
        define('WP_AUTO_UPDATE_CORE', true);
        define('AUTOMATIC_UPDATER_DISABLED', false);
        define('WP_HOME', 'https://guijiulin.cn');
        define('WP_SITEURL', 'https://guijiulin.cn');
        define('WP_CACHE', true);
        define('WP_REDIS_HOST', 'redis');
        define('WP_REDIS_PORT', 6379);
        define('WP_REDIS_TIMEOUT', 1);
        define('WP_REDIS_READ_TIMEOUT', 1);
        define('WP_REDIS_DATABASE', 0);
    volumes:
      - ./wordpress_data:/var/www/html
      - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
      - ./object-cache.php:/var/www/html/wp-content/object-cache.php
    networks:
      - wp-network
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: '1'
        reservations:
          memory: 256M
          cpus: '0.25'

  nginx:
    depends_on:
      - wordpress
    image: nginx:alpine
    container_name: wordpress_nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx-conf/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/ssl:ro
      - ./wordpress_data:/var/www/html:ro
      - ./nginx-conf/cache:/var/cache/nginx
      - ./nginx-conf/logs:/var/log/nginx
    networks:
      - wp-network
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 128M
    healthcheck:
      test: ["CMD", "nginx", "-t"]
      interval: 30s
      timeout: 10s
      retries: 3

networks:
  wp-network:
    driver: bridge
    driver_opts:
      com.docker.network.driver.mtu: 1500
EOF

7. 创建 Redis 对象缓存文件

cat > object-cache.php << 'EOF'
<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {
    global $wp_object_cache;
    return $wp_object_cache->add( $key, $data, $group, $expire );
}

function wp_cache_close() {
    return true;
}

function wp_cache_decr( $key, $offset = 1, $group = '' ) {
    global $wp_object_cache;
    return $wp_object_cache->decr( $key, $offset, $group );
}

function wp_cache_delete( $key, $group = '' ) {
    global $wp_object_cache;
    return $wp_object_cache->delete( $key, $group );
}

function wp_cache_flush() {
    global $wp_object_cache;
    return $wp_object_cache->flush();
}

function wp_cache_get( $key, $group = '', $force = false, &$found = null ) {
    global $wp_object_cache;
    return $wp_object_cache->get( $key, $group, $force, $found );
}

function wp_cache_incr( $key, $offset = 1, $group = '' ) {
    global $wp_object_cache;
    return $wp_object_cache->incr( $key, $offset, $group );
}

function wp_cache_init() {
    global $wp_object_cache;
    if ( ! ( $wp_object_cache instanceof WP_Object_Cache ) ) {
        $wp_object_cache = new WP_Object_Cache();
    }
}

function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) {
    global $wp_object_cache;
    return $wp_object_cache->replace( $key, $data, $group, $expire );
}

function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
    global $wp_object_cache;
    return $wp_object_cache->set( $key, $data, $group, $expire );
}

function wp_cache_switch_to_blog( $blog_id ) {
    global $wp_object_cache;
    return $wp_object_cache->switch_to_blog( $blog_id );
}

function wp_cache_add_global_groups( $groups ) {
    global $wp_object_cache;
    $wp_object_cache->add_global_groups( $groups );
}

function wp_cache_add_non_persistent_groups( $groups ) {
    global $wp_object_cache;
    $wp_object_cache->add_non_persistent_groups( $groups );
}

class WP_Object_Cache {
    private $redis;
    private $cache = [];
    private $global_groups = [];
    private $non_persistent_groups = [];
    private $blog_prefix;
    private $default_expiration = 3600;

    public function __construct() {
        $this->blog_prefix = get_current_blog_id();
        
        try {
            $this->redis = new Redis();
            $this->redis->connect(
                defined('WP_REDIS_HOST') ? WP_REDIS_HOST : 'redis',
                defined('WP_REDIS_PORT') ? WP_REDIS_PORT : 6379,
                defined('WP_REDIS_TIMEOUT') ? WP_REDIS_TIMEOUT : 1
            );
            
            if ( defined('WP_REDIS_PASSWORD') && WP_REDIS_PASSWORD ) {
                $this->redis->auth(WP_REDIS_PASSWORD);
            }
            
            if ( defined('WP_REDIS_DATABASE') ) {
                $this->redis->select(WP_REDIS_DATABASE);
            }
            
            $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
            $this->redis->setOption(Redis::OPT_READ_TIMEOUT, defined('WP_REDIS_READ_TIMEOUT') ? WP_REDIS_READ_TIMEOUT : 1);
            
        } catch (Exception $e) {
            $this->redis = false;
        }
    }

    private function build_key( $key, $group = 'default' ) {
        if ( empty($group) ) {
            $group = 'default';
        }
        
        if ( in_array($group, $this->global_groups) ) {
            $prefix = '';
        } else {
            $prefix = $this->blog_prefix . ':';
        }
        
        return $prefix . $group . ':' . $key;
    }

    public function add( $key, $data, $group = 'default', $expire = 0 ) {
        if ( wp_suspend_cache_addition() ) {
            return false;
        }
        
        $key = $this->build_key($key, $group);
        
        if ( in_array($group, $this->non_persistent_groups) ) {
            if ( isset($this->cache[$key]) ) {
                return false;
            }
            $this->cache[$key] = $data;
            return true;
        }
        
        if ( ! $this->redis ) {
            return false;
        }
        
        if ( $this->redis->exists($key) ) {
            return false;
        }
        
        $expire = $expire ?: $this->default_expiration;
        return $this->redis->setex($key, $expire, $data);
    }

    public function decr( $key, $offset = 1, $group = 'default' ) {
        $key = $this->build_key($key, $group);
        
        if ( in_array($group, $this->non_persistent_groups) ) {
            if ( ! isset($this->cache[$key]) || ! is_numeric($this->cache[$key]) ) {
                return false;
            }
            $this->cache[$key] -= $offset;
            return $this->cache[$key];
        }
        
        if ( ! $this->redis ) {
            return false;
        }
        
        return $this->redis->decrBy($key, $offset);
    }

    public function delete( $key, $group = 'default' ) {
        $key = $this->build_key($key, $group);
        unset($this->cache[$key]);
        
        if ( in_array($group, $this->non_persistent_groups) ) {
            return true;
        }
        
        if ( ! $this->redis ) {
            return false;
        }
        
        return (bool) $this->redis->del($key);
    }

    public function flush() {
        $this->cache = [];
        
        if ( ! $this->redis ) {
            return false;
        }
        
        return $this->redis->flushDB();
    }

    public function get( $key, $group = 'default', $force = false, &$found = null ) {
        $key = $this->build_key($key, $group);
        
        if ( ! $force && isset($this->cache[$key]) ) {
            $found = true;
            return $this->cache[$key];
        }
        
        if ( in_array($group, $this->non_persistent_groups) ) {
            $found = false;
            return false;
        }
        
        if ( ! $this->redis ) {
            $found = false;
            return false;
        }
        
        $data = $this->redis->get($key);
        if ( $data === false ) {
            $found = false;
            return false;
        }
        
        $found = true;
        $this->cache[$key] = $data;
        return $data;
    }

    public function incr( $key, $offset = 1, $group = 'default' ) {
        $key = $this->build_key($key, $group);
        
        if ( in_array($group, $this->non_persistent_groups) ) {
            if ( ! isset($this->cache[$key]) || ! is_numeric($this->cache[$key]) ) {
                return false;
            }
            $this->cache[$key] += $offset;
            return $this->cache[$key];
        }
        
        if ( ! $this->redis ) {
            return false;
        }
        
        return $this->redis->incrBy($key, $offset);
    }

    public function replace( $key, $data, $group = 'default', $expire = 0 ) {
        $key = $this->build_key($key, $group);
        
        if ( in_array($group, $this->non_persistent_groups) ) {
            if ( ! isset($this->cache[$key]) ) {
                return false;
            }
            $this->cache[$key] = $data;
            return true;
        }
        
        if ( ! $this->redis ) {
            return false;
        }
        
        if ( ! $this->redis->exists($key) ) {
            return false;
        }
        
        $expire = $expire ?: $this->default_expiration;
        return $this->redis->setex($key, $expire, $data);
    }

    public function set( $key, $data, $group = 'default', $expire = 0 ) {
        $key = $this->build_key($key, $group);
        $this->cache[$key] = $data;
        
        if ( in_array($group, $this->non_persistent_groups) ) {
            return true;
        }
        
        if ( ! $this->redis ) {
            return false;
        }
        
        $expire = $expire ?: $this->default_expiration;
        return $this->redis->setex($key, $expire, $data);
    }

    public function switch_to_blog( $blog_id ) {
        $this->blog_prefix = $blog_id;
    }

    public function add_global_groups( $groups ) {
        $groups = (array) $groups;
        $this->global_groups = array_merge($this->global_groups, $groups);
        $this->global_groups = array_unique($this->global_groups);
    }

    public function add_non_persistent_groups( $groups ) {
        $groups = (array) $groups;
        $this->non_persistent_groups = array_merge($this->non_persistent_groups, $groups);
        $this->non_persistent_groups = array_unique($this->non_persistent_groups);
    }
}

wp_cache_init();
EOF

8. 启动服务

# 设置目录权限
chmod -R 755 /opt/wordpress
chown -R www-data:www-data /opt/wordpress/wordpress_data

# 启动服务
cd /opt/wordpress
docker-compose up -d

# 查看服务状态
docker-compose ps

# 查看日志
docker-compose logs -f

需要修改的地方:

  1. SSL证书:需要将您的SSL证书(guijiulin.cn.crtguijiulin.cn.key)放到 /opt/wordpress/certs/ 目录

  2. 域名配置:在 nginx.conf 文件中,将 guijiulin.cn 替换为您的实际域名

  3. 性能调优:根据服务器实际资源调整 docker-compose.yml 中的内存和CPU限制

  4. Redis密码:如果需要Redis密码,在 .env 文件中取消注释并设置

主要优化点:

  1. 性能优化

    • MariaDB配置了查询缓存和性能优化
    • Redis作为对象缓存,大幅提升WordPress速度
    • Nginx配置了HTTP/2、Gzip压缩和缓存
    • PHP配置了OPcache
  2. 安全性

    • 密码以base64加密形式存储
    • 强制HTTPS
    • 安全HTTP头
    • 禁用敏感文件访问
  3. 高可用性

    • 健康检查
    • 资源限制
    • 自动重启
  4. 监控和日志

    • 慢查询日志
    • 访问日志
    • 错误日志

这个配置将为您的WordPress站点提供极快的访问速度和优秀的性能表现。