Все работает нормально, за исключением обновления плагинов из wp-admin. Любое обновление плагина (а не только одного конкретного плагина) завершается с ошибкой, например:
Обновление плагина ACF Content Analysis для Yoast SEO (1/24) Загрузка
обновления с
https://downloads.wordpress.org/plugin/ ... eo.3.2.zip…
Произошла ошибка при обновлении ACF Content Analysis для Yoast. SEO:
Загрузка не удалась. Каталог назначения для потоковой передачи файлов
не существует или недоступен для записи.
Поздние попытки приводили к следующим вариантам:
PCLZIP_ERR_MISSING_FILE (-4): Отсутствует архивный файл
'/var/www/html/wp-content/upgrade/…zip'
Не удалось создать каталог...
Не удалось создать каталог обновления-временного резервного копирования
Наконец: обновление невозможно установить, поскольку некоторые файлы не удалось скопировать. Обычно это происходит из-за несогласованных прав доступа к файлам.
По сути: все обновления плагинов завершаются с ошибкой на разных этапах файловой системы (загрузка, распаковка, резервное копирование или копирование).
Среда
- Ubuntu VPS
- Docker без root (запускается от имени пользователя без полномочий root, сокет
- /run/user/1000/docker.sock)
- Обратный прокси-сервер Traefik + Cloudflare DNS-01
- WordPress wordpress:php8.3-fpm за nginx:alpine
- MariaDB 11.8
- Привязка кодовой базы с хоста: ./html:/var/www/html
Код: Выделить всё
services:
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
command:
- --global.checkNewVersion=true
- --global.sendAnonymousUsage=false
# Providers
- --providers.docker=true
- --providers.docker.endpoint=unix:///var/run/docker.sock
- --providers.docker.exposedByDefault=false
- --providers.docker.network=web
- --providers.file.directory=/dynamic
- --providers.file.watch=true
# Entrypoints
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls=true
- --entrypoints.websecure.http.tls.certResolver=cfdns
# TLS Options
- --entrypoints.websecure.http.tls.options=default
# API
- --api.insecure=true
- --api.dashboard=true
# Logging
- --log.level=INFO
- --accessLog=true
# ACME via Cloudflare DNS
- --certificatesResolvers.cfdns.acme.email=contact@corp.com
- --certificatesResolvers.cfdns.acme.storage=/letsencrypt/acme.json
- --certificatesResolvers.cfdns.acme.keyType=EC256
- --certificatesResolvers.cfdns.acme.dnsChallenge.delayBeforeCheck=30
- --certificatesResolvers.cfdns.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53
- --certificatesResolvers.cfdns.acme.dnsChallenge.provider=cloudflare
- --ping=true
ports:
- "80:80"
- "443:443"
- "127.0.0.1:8080:8080"
environment:
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
volumes:
- /run/user/1000/docker.sock:/var/run/docker.sock:ro
- ./dynamic:/dynamic:ro
- ./letsencrypt:/letsencrypt
- ./logs:/var/log/traefik
networks:
- web
networks:
web:
external: true
Код: Выделить всё
http:
middlewares:
security-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true
contentTypeNosniff: true
referrerPolicy: no-referrer-when-downgrade
browserXssFilter: true
permissionsPolicy: >-
geolocation=(), microphone=(), camera=(), fullscreen=(self)
gzip:
compress: {}
rate-limit:
rateLimit:
average: 100
burst: 50
tls:
options:
default:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
sniStrict: true
curvePreferences:
- CurveP521
- CurveP384
Код: Выделить всё
networks:
web: # external, only for Traefik nginx
external: true
example_net: # internal, unique to this stack
external: false
volumes:
redisdata:
nginx_cache: {}
services:
db:
image: mariadb:11.8
restart: unless-stopped
environment:
MARIADB_DATABASE: ${MARIADB_DATABASE}
MARIADB_USER: ${MARIADB_USER}
MARIADB_PASSWORD: ${MARIADB_PASSWORD}
MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
command: ["--transaction-isolation=READ-COMMITTED", "--binlog_expire_logs_seconds=2592000", "--innodb_buffer_pool_size=512M"]
volumes:
- ./db:/var/lib/mysql
networks: [example_net]
healthcheck:
test: ["CMD-SHELL", "mariadb-admin ping -uroot -p${MARIADB_ROOT_PASSWORD} --silent"]
interval: 10s
timeout: 5s
retries: 5
wordpress:
image: wordpress:php8.3-fpm
restart: unless-stopped
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_NAME: ${MARIADB_DATABASE}
WORDPRESS_DB_USER: ${MARIADB_USER}
WORDPRESS_DB_PASSWORD: ${MARIADB_PASSWORD}
WP_REDIS_HOST: redis
WP_REDIS_PORT: 6379
volumes:
- ./html:/var/www/html # bind mount from host
depends_on:
db:
condition: service_healthy
networks: [example_net]
nginx:
image: nginx:alpine
restart: unless-stopped
depends_on:
- wordpress
volumes:
- ./html:/var/www/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./cache:/var/cache/nginx
- nginx_cache:/var/cache/nginx
networks: [web, example_net]
labels:
- "traefik.enable=true"
- "traefik.docker.network=web"
# HTTP → HTTPS redirect
- "traefik.http.middlewares.example-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.example-redirect.redirectscheme.permanent=true"
- "traefik.http.routers.example-https.middlewares=security-headers@file,gzip@file,rate-limit@file"
# HTTP router
- "traefik.http.routers.example-http.rule=Host(`example.com`) || Host(`www.example.com`)"
- "traefik.http.routers.example-http.entrypoints=web"
- "traefik.http.routers.example-http.middlewares=example-redirect"
- "traefik.http.routers.example-http.service=example"
# HTTPS router
- "traefik.http.routers.example-https.rule=Host(`example.com`) || Host(`www.example.com`)"
- "traefik.http.routers.example-https.entrypoints=websecure"
- "traefik.http.routers.example-https.tls=true"
- "traefik.http.routers.example-https.tls.certresolver=cfdns"
- "traefik.http.routers.example-https.service=example"
# Explicit service (points at Nginx in the container)
- "traefik.http.services.example.loadbalancer.server.port=80"
redis:
image: redis:7.4-alpine
restart: unless-stopped
command: ["redis-server", "--save", "", "--appendonly", "no"]
volumes:
- redisdata:/data
networks: [example_net]
Код: Выделить всё
fastcgi_cache_key "$scheme$request_method$host$request_uri";
server {
listen 80;
server_name _;
root /var/www/html;
index index.php index.html;
# Skip cache for logged-in, POST, search, preview, feeds
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($http_cookie ~* "comment_author|wordpress_logged_in|wp-postpass") { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin/|/wp-login.php|/xmlrpc.php|/feed|/preview=") { set $skip_cache 1; }
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass wordpress:9000;
fastcgi_cache WPFC;
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_min_uses 2;
add_header X-FastCGI-Cache $upstream_cache_status;
fastcgi_no_cache $skip_cache;
fastcgi_cache_bypass $skip_cache;
fastcgi_read_timeout 120s;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
}
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|webp|avif)$ {
expires 30d;
access_log off;
}
client_max_body_size 64m;
}
На хосте (как пользователь развертывания, запускающий Docker без root-доступа), я пробовал:
Изменить владельца папки, смонтированной привязкой:
Код: Выделить всё
sudo chown -R 33:33 ./html # www-data
sudo chown -R deploy:deploy ./html
Код: Выделить всё
find html -type d -exec chmod 755 {} \;
find html -type f -exec chmod 644 {} \;
chmod 777 html/wp-content
chmod -R 777 html/wp-content/plugins
chmod -R 777 html/wp-content/upgrade
chmod -R 777 html/wp-content/upgrade-temp-backup
chmod -R 777 html/wp-content/uploads
Код: Выделить всё
define('FS_METHOD', 'direct');
Проверка WP_TEMP_DIR (удалены все старые значения с предыдущего хостинга).
Даже после этих изменений любое обновление плагина по-прежнему завершается с ошибкой, часто с:
Обновление не может быть установлено, поскольку некоторые файлы не удалось скопировать.
Обычно это происходит из-за несогласованных прав доступа к файлам.
/>
Значит, это влияет на все плагины, а не только на ACF/Yoast.
Вопрос
Учитывая, что это:
- WordPress в WordPress: PHP8.3-fpm
- За Nginx и Traefik
- Работа в безрутовых системах Docker
- С файлами WP, привязанными к хосту (./html:/var/www/html).
обновления плагина/темы/ядра WordPress от wp-admin надежно работали для всех плагинов, и
я не нужно каждый раз вводить chmod 777 или каждый раз вручную удалять/переустанавливать плагины?
В частности, существует ли рекомендуемый шаблон для WordPress + Docker без root (именованные тома или привязки, какой пользователь должен владеть /var/www/html и т. д.), который вообще позволяет избежать этих ошибок «Каталог назначения не доступен для записи / противоречивые разрешения»?
Подробнее здесь: https://stackoverflow.com/questions/798 ... sion-issue
Мобильная версия