Wordpress na docker serwowany traefik'iem
Jak szybko postawić Wordpressa na dockerach i łatwo tworzyć nowe treści przy pomocy zdalnego AI.
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.
- 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 updatesudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginsudo systemctl enable --now docker- 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" -Rservices:
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- 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
- 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=''
- FAZA 1 - Gdzie hashe Wordpressa, generujemy swój, pod:
https://api.wordpress.org/secret-key/1.1/salt/- 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ę $$)
- FAZA 1 - Następnie tworzymy wymagane katalogi:
mkdir ./katalog2/mysql
mkdir ./katalog2/nginx
mkdir ./katalog2/redis
mkdir ./katalog2/wordpress
- 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
~ - 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- 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
- 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- 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"]
- 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...