Wordpress na docker serwowany traefik'iem

Jak szybko postawić Wordpressa na dockerach i łatwo tworzyć nowe treści przy pomocy zdalnego AI.

Wordpress na docker serwowany traefik'iem

Już jest w Internecie sporo poradników instalacji i konfiguracji hostowania strony w Wordpress na dockerach i serwowany traefikiem, jednak podzielę się moim setup'em, który łatwo można połączyć z agentami AI.

  1. FAZA 1 - Instalacja silnika docker na systemie Ubuntu 26.04:
# Add Docker's official GPG key:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
  1. FAZA 1 - Jeżeli chcemy uruchamiać dockery z konta innego użytkownika niż root (podniesienie bezpieczeństwa serwera):
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker
sudo chown "$USER":"$USER" /home/"$USER"/.docker -R
sudo chmod g+rwx "$HOME/.docker" -R
services:
  traefik:
    image: traefik:latest
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    command:
      - --global.sendAnonymousUsage=false
      - --accesslog
      - --log.level=INFO
      - --api.dashboard=false
      - --api.insecure=false
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.web-secure.address=:443
      - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
      - --certificatesresolvers.letsencrypt.acme.email=<twój-adres-email@example.com>
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
      - --certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=web
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./letsencrypt:/letsencrypt
    labels:
      - traefik.enable=true
      - traefik.http.routers.api.entrypoints=web
      - traefik.http.routers.api.service=api@internal
      - traefik.http.routers.api.entrypoints=web-secure
      - traefik.http.routers.api.tls=true
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"
    networks:
      - proxy
      - backend

networks:
  proxy: # this is the network provided by traefik
    external: true
  backend:
    external: false
  1. FAZA 1 - Następnie w tym samym pliku poniżej lub w nowym pliku katalog2/docker-compose.yml umieszczamy definicję Wordpressa:
services:
  nginx:
    image: nginx:alpine
    container_name: nginx
    restart: unless-stopped
    depends_on:
      - wordpress
    expose:
      - 80
      - 443
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./nginx/logs:/var/log/nginx
      - ./wordpress:/var/www/html
    labels:
      - "traefik.enable=true"
      # ---- router HTTP: tylko przekierowanie na HTTPS ----
      - "traefik.http.routers.wordpress.rule=Host(`${WP_URL}`) || Host(`${WP_WWW_URL}`)"
      - "traefik.http.routers.wordpress.entrypoints=web"
      - "traefik.http.routers.wordpress.middlewares=redirect-to-https"

      # ---- router HTTPS: tu realnie ląduje ruch ----
      - "traefik.http.routers.wordpress-secure.rule=Host(`${WP_URL}`) || Host(`${WP_WWW_URL}`)"
      - "traefik.http.routers.wordpress-secure.entrypoints=web-secure"
      - "traefik.http.routers.wordpress-secure.tls=true"
      - "traefik.http.routers.wordpress-secure.tls.certresolver=letsencrypt"
      - "traefik.http.routers.wordpress-secure.middlewares=redirect-to-www,wordpress-auth,ratelimit" # z auth
      #- "traefik.http.routers.wordpress-secure.middlewares=ratelimit"

      # ---- middleware: http -> https ----
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true"

      # ---- middleware: domena bez www -> z www ----
      - "traefik.http.middlewares.redirect-to-www.redirectregex.regex=^https://${WP_WWW_URL}/(.*)"
      - "traefik.http.middlewares.redirect-to-www.redirectregex.replacement=https://${WP_URL}/$${1}"
      - "traefik.http.middlewares.redirect-to-www.redirectregex.permanent=true"

      # ---- rate limit ----
      - "traefik.http.middlewares.ratelimit.ratelimit.average=5000000"
      - "traefik.http.middlewares.ratelimit.ratelimit.period=10s"

      # ---- basic auth ----
      - "traefik.http.middlewares.wordpress-auth.basicauth.users=user:$$2y$$10$$Yl6wD6JsUl8AnjvMCemju.zjV7rpE4YYBZ48jUxOSqozpZJsh7R3O"
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"
    networks:
      - backend
      - proxy

  wordpress:
    image: wordpress:php8.5-fpm
    container_name: wordpress
    restart: unless-stopped
    depends_on: 
      - mysql
      - redis
    expose:
      - 9000
    environment:
      PHP_MAX_INPUT_VARS: 4000
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_NAME: ${WP_DB_NAME}
      WORDPRESS_DB_USER: ${WP_DB_USER}
      WORDPRESS_DB_PASSWORD: ${WP_DB_PASSWORD}
      WORDPRESS_TABLE_PREFIX: ${WP_DB_PREFIX}
      WORDPRESS_AUTH_KEY: ${WORDPRESS_AUTH_KEY}
      WORDPRESS_SECURE_AUTH_KEY: ${WORDPRESS_SECURE_AUTH_KEY}
      WORDPRESS_LOGGED_IN_KEY: ${WORDPRESS_LOGGED_IN_KEY}
      WORDPRESS_NONCE_KEY: ${WORDPRESS_NONCE_KEY}
      WORDPRESS_AUTH_SALT: ${WORDPRESS_AUTH_SALT}
      WORDPRESS_SECURE_AUTH_SALT: ${WORDPRESS_SECURE_AUTH_SALT}
      WORDPRESS_LOGGED_IN_SALT: ${WORDPRESS_LOGGED_IN_SALT}
      WORDPRESS_NONCE_SALT: ${WORDPRESS_NONCE_SALT}
      WORDPRESS_CONFIG_EXTRA:
        define( 'WP_REDIS_HOST', 'redis' );
        define( 'WP_REDIS_PORT', 6379 );
        define( 'COMPRESS_CSS', true );
        define( 'COMPRESS_SCRIPTS', true );
        define( 'CONCATENATE_SCRIPTS', true );
        define( 'ENFORCE_GZIP', false );
        define( 'AUTOSAVE_INTERVAL', 120 );
        define( 'WP_POST_REVISIONS', 10);
        define( 'MEDIA_TRASH', true );
        define( 'EMPTY_TRASH_DAYS', 30 );
        define( 'IMAGE_EDIT_OVERWRITE', true );
        define( 'DISALLOW_FILE_EDIT', true );
        define( 'DISABLE_WP_CRON', true );
    volumes:
      - ./wordpress:/var/www/html
      - ./wordpress.ini:/usr/local/etc/php/conf.d/wordpress.ini
      - ./php-miles.conf:/usr/local/etc/php-fpm.d/zz-miles.conf
    labels:
      - traefik.enable=true
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"
    networks:
      - backend

  mysql:
    image: mysql:9.7
    container_name: mysql
    restart: unless-stopped
    expose:
      - 3306
    environment:
      MYSQL_DATABASE: ${WP_DB_NAME}
      MYSQL_USER: ${WP_DB_USER}
      MYSQL_PASSWORD: ${WP_DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    volumes:
      - ./mysql:/var/lib/mysql
    labels:
      - traefik.enable=true
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"
    networks:
      - backend

  redis:
    image: redis:alpine
    container_name: redis
    restart: unless-stopped
    expose:
      - 6379
    command: 
      - redis-server
      - --save 60 1 
      - --loglevel warning
      - --maxmemory 256mb 
      - --maxmemory-policy allkeys-lru 
    volumes:
      - ./redis:/var/lib/redis
      - ./redis:/data
      #- ./redis.conf:/usr/local/etc/redis/redis.conf
    labels:
      - traefik.enable=true
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"
    networks:
      - backend

networks:
  proxy: # this is the network provided by traefik
    external: true
  backend:
    external: false
  1. FAZA 1 - Tworzymy nastepnie plik z zmienymi środowiskowymi pasującego do katalog2/docker-compose.yml, czyli:
# Database
WP_DB_NAME=
WP_DB_PREFIX=wp_
WP_DB_USER=
WP_DB_PASSWORD=
MYSQL_ROOT_PASSWORD=
# Domain
WP_URL=
WP_WWW_URL=www.
# Wordpress
WORDPRESS_AUTH_KEY=''
WORDPRESS_SECURE_AUTH_KEY=''
WORDPRESS_LOGGED_IN_KEY=''
WORDPRESS_NONCE_KEY=''
WORDPRESS_AUTH_SALT=''
WORDPRESS_SECURE_AUTH_SALT=''
WORDPRESS_LOGGED_IN_SALT=''
WORDPRESS_NONCE_SALT=''
  1. FAZA 1 - Gdzie hashe Wordpressa, generujemy swój, pod:
https://api.wordpress.org/secret-key/1.1/salt/
  1. FAZA 1 - Gdzie hasło do opcjonalnego Auth treafika generujemy, jak poniżej:
htpasswd -nbBC 10 "" "haslo"

Potem w każdym $ podwajamy go (robi się $$)

  1. FAZA 1 - Następnie tworzymy wymagane katalogi:
mkdir ./katalog2/mysql
mkdir ./katalog2/nginx
mkdir ./katalog2/redis
mkdir ./katalog2/wordpress
  1. FAZA 1 - Opcjonalnie tworzymy ./katalog2/wordpress.ini np. z zawartościa:
file_uploads = On
upload_max_filesize = 1024M
post_max_size = 1024M
memory_limit = 1024M
max_execution_time = 600
max_input_vars = 4000
~                       
  1. FAZA 1 - Na koniec aby wystartować cały setup uruchamiamy traefika i Wordpressa:
cd ./katalog1/ 
docker compose up -d
cd ./katalog2/
docker compose up -d
  1. FAZA 2 - tworzenie dostępu dla agentów AI. Generujemy klucze SSH i wgrywamy go na zdalny serwer:
ssh-keygen -t ed25519
ssh-copy-id user@serwer

  1. FAZA2 - konfiguracja definicji dostępu SSH dla danego użytkownika - umieszczamy w pliku ~/.ssh/config:
Host mojserwer
    HostName 192.0.2.10
    User user
    Port 22
    IdentityFile ~/.ssh/id_ed25519
  1. FAZA 3 - Uruchomenie agenta AI:

Uruchamiamy agenta np. Claude Desktop lub Claude CLI i po prostu piszemy:

Na serwerze takim i takim, do którego masz dostep przez SSH kluczem, w katalogu takim i takim jest setup Wordpressa na dockerach. Przejrzyj kod Wordpressa i zrób to i to.
services:
  wordpress-cli:
#    image: wordpress:cli-2-php8.4
    build: ./wp-cli
    container_name: wordpress-cli
    restart: unless-stopped
    # uid:gid dopasowane do właściciela plików WordPress/cache (www-data z kontenera FPM =
    # uid 33). Bez tego CLI (obraz Alpine, www-data=82) nie mógł kasować plików page cache.
    user: "33:33"
    volumes:
      - ./wp-cli:/wp-cli
      - ./wordpress:/var/www/html
      - ./wordpress.ini:/usr/local/etc/php/conf.d/wordpress.ini
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_NAME: ${WP_DB_NAME}
      WORDPRESS_DB_USER: ${WP_DB_USER}
      WORDPRESS_DB_PASSWORD: ${WP_DB_PASSWORD}
      WORDPRESS_TABLE_PREFIX: ${WP_DB_PREFIX}
      WORDPRESS_CONFIG_EXTRA:
        define( 'WP_REDIS_HOST', 'redis' );
        define( 'WP_REDIS_PORT', 6379 );
        define( 'DISABLE_WP_CRON', true );
    working_dir: /var/www/html
    labels:
      - traefik.enable=true
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"
    networks:
      - backend

networks:
  proxy: # this is the network provided by traefik
    external: true
  backend:
    external: false

#gdzie w ./katalog2/wp-cli/Dockerfile umieszczamy:
#FROM wordpress:cli-2-php8.4

#CMD ["sleep", "infinity"]
  1. FAZA 3 - Uruchomienie kontenera z WP-CLI i przykład użycia agentem:
docker compose -f docker-compose-wp-cli.yml up -d

Następnie piszemy do agenta AI np.:

Przy pomocy kontenera wordpress-cli i kodu wordpressa aktywuj plugin / dodaj do sklepu wooCommerce nowy produkt o opisie ... / wyczyść cache i rozgrzej stronę / dodaj nowy wpis / i wiele wiele innych...