Table of Contents

wordpress multisite mit docker hardened images

Anpassbares Beispiel mit DHI‑Images, .env und einer Nginx‑Server‑Config für eine Subdomain‑Multisite hinter Traefik (TLS‑Termination in Traefik). Die Config orientiert sich an gängigen Docker‑/Traefik‑WordPress‑Setups und Nginx‑FPM‑Beispielen.^1


`.env` Beispiel

snippet.env
# Basis
COMPOSE_PROJECT_NAME=wpms
DOMAIN=example.com
 
# Datenbank
MYSQL_ROOT_PASSWORD=super-secret-root
MYSQL_DATABASE=wordpress
MYSQL_USER=wpuser
MYSQL_PASSWORD=super-secret-wp
 
# WordPress Multisite
WP_TITLE="My Multisite"
WP_ADMIN_USER=admin
WP_ADMIN_PASSWORD=super-secret-admin
WP_ADMIN_EMAIL=admin@example.com
 
# Traefik / Routing
TRAEFIK_ENTRYPOINT=websecure
TRAEFIK_ROUTER_NAME=wpms
TRAEFIK_HOSTRULE=HostRegexp(`{subdomain:[a-z0-9-]+}.example.com`) || Host(`example.com`)

`docker-compose.yml` Skelett

snippet.yaml
version: "3.9"

services:
  nginx:
    image: dhi/nginx:latest
    depends_on:
      - php
    volumes:
      - wp_data:/var/www/html
      - ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf:ro
    networks:
      - web
      - internal
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=web"
      - "traefik.http.routers.${TRAEFIK_ROUTER_NAME}.rule=${TRAEFIK_HOSTRULE}"
      - "traefik.http.routers.${TRAEFIK_ROUTER_NAME}.entrypoints=${TRAEFIK_ENTRYPOINT}"
      - "traefik.http.routers.${TRAEFIK_ROUTER_NAME}.tls=true"
      - "traefik.http.services.${TRAEFIK_ROUTER_NAME}.loadbalancer.server.port=80"

  php:
    image: dhi/php:latest
    depends_on:
      - db
      - redis
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
      WORDPRESS_DB_USER: ${MYSQL_USER}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
 
      # Multisite
      WORDPRESS_CONFIG_EXTRA: |
        define('WP_ALLOW_MULTISITE', true);
        define('MULTISITE', true);
        define('SUBDOMAIN_INSTALL', true);
        define('DOMAIN_CURRENT_SITE', '${DOMAIN}');
        define('PATH_CURRENT_SITE', '/');
        define('SITE_ID_CURRENT_SITE', 1);
        define('BLOG_ID_CURRENT_SITE', 1);
        define('WP_DEBUG', false);
        define('WP_REDIS_HOST', 'redis');
        define('WP_REDIS_PORT', 6379);
    volumes:
      - wp_data:/var/www/html
    networks:
      - internal

  db:
    image: dhi/mysql:latest
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - internal

  redis:
    image: dhi/redis:latest
    networks:
      - internal

networks:
  web:
    external: true     # existierendes Traefik-Netz
  internal:
    internal: true

volumes:
  wp_data:
  db_data:

Nginx‑Server‑Config `nginx/conf.d/default.conf`

snippet.nginx
server {
    listen 80;
    server_name ${DOMAIN} *.${DOMAIN};
    root /var/www/html;
    index index.php;
 
    # Von Traefik weitergereichte IP
    real_ip_header X-Forwarded-For;
    set_real_ip_from 0.0.0.0/0;
 
    # Logs (optional)
    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;
 
    # Statische Dateien direkt ausliefern
    location = /favicon.ico { log_not_found off; access_log off; }
    location = /robots.txt  { log_not_found off; access_log off; }
 
    location ~* \.(css|gif|ico|jpeg|jpg|js|png|svg|webp|woff2?)$ {
        expires max;
        log_not_found off;
    }
 
    # Kein direkter PHP‑Zugriff in Uploads
    location ~* ^/wp-content/uploads/.*\.php$ {
        deny all;
    }
 
    # Haupt‑Rewrite für WordPress / Multisite
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
 
    # PHP an FPM durchreichen
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS off;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
    }
}

Hinweise zur Verwendung