version: "3.9" networks: appNet: { external: true } privNet: driver: overlay internal: true attachable: false driver_opts: { encrypted: "true" } secrets: mysql_root_password___DOMAIN__: { external: true } mysql_wp_password___DOMAIN__: { external: true } x-common-env: &common_env WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD_FILE: /run/secrets/mysql_wp_password___DOMAIN__ WORDPRESS_DB_NAME: wordpress WORDPRESS_CONFIG_EXTRA: | if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { $_SERVER['HTTPS'] = 'on'; } define('FORCE_SSL_ADMIN', true); define('WP_CACHE_KEY_SALT', '__DOMAIN__'); define('WP_REDIS_HOST', 'redis'); services: db: image: mysql:8.4 command: ["--innodb-buffer-pool-size=192M","--max-connections=100","--skip-log-bin"] environment: MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password___DOMAIN__ MYSQL_PASSWORD_FILE: /run/secrets/mysql_wp_password___DOMAIN__ secrets: [mysql_root_password___DOMAIN__, mysql_wp_password___DOMAIN__] networks: [privNet] volumes: - type: bind source: /mnt/data/sites/__DOMAIN__/db target: /var/lib/mysql deploy: placement: { constraints: ["node.labels.pool == mysql","node.role == worker"] } resources: limits: { cpus: "0.50", memory: 512M } reservations: { cpus: "0.10", memory: 256M } healthcheck: test: ["CMD-SHELL","MYSQL_PWD=$$(cat /run/secrets/mysql_root_password___DOMAIN__) mysqladmin ping -h 127.0.0.1 -uroot"] interval: 20s timeout: 5s retries: 5 redis: image: redis:7-alpine command: ["redis-server","--appendonly","yes","--maxmemory","96mb","--maxmemory-policy","allkeys-lru"] networks: [privNet] volumes: - type: bind source: /mnt/data/sites/__DOMAIN__/redis target: /data deploy: placement: { constraints: ["node.labels.pool == redis","node.role == worker"] } resources: limits: { cpus: "0.20", memory: 192M } reservations: { cpus: "0.05", memory: 64M } healthcheck: test: ["CMD","redis-cli","ping"] interval: 20s timeout: 3s retries: 5 php: image: wordpress:php8.3-fpm environment: *common_env secrets: [mysql_wp_password___DOMAIN__] networks: [appNet, privNet] volumes: - type: bind source: /mnt/data/sites/__DOMAIN__/code target: /var/www/html - type: bind source: /mnt/data/sites/__DOMAIN__/uploads target: /var/www/html/wp-content/uploads - type: bind source: /mnt/data/sites/__DOMAIN__/plugins target: /var/www/html/wp-content/plugins - type: bind source: /mnt/data/sites/__DOMAIN__/themes target: /var/www/html/wp-content/themes - type: bind source: /mnt/data/sites/__DOMAIN__/mu-plugins target: /var/www/html/wp-content/mu-plugins tmpfs: ["/tmp"] security_opt: ["no-new-privileges:true"] cap_drop: ["ALL"] ulimits: { nofile: 65536, nproc: 4096 } deploy: placement: { constraints: ["node.labels.pool == php","node.role == worker"] } resources: limits: { cpus: "0.50", memory: 512M } reservations: { cpus: "0.10", memory: 128M } nginx: image: nginx:stable-alpine networks: [appNet] depends_on: [php] volumes: - type: bind source: /mnt/data/sites/__DOMAIN__/code target: /var/www/html:ro - type: bind source: /mnt/data/sites/__DOMAIN__/nginx/conf.d target: /etc/nginx/conf.d read_only: true tmpfs: ["/var/cache/nginx","/var/run"] security_opt: ["no-new-privileges:true"] cap_drop: ["ALL"] ulimits: { nofile: 65536 } deploy: placement: { constraints: ["node.labels.pool == nginx","node.role == worker"] } resources: limits: { cpus: "0.25", memory: 256M } reservations: { cpus: "0.05", memory: 64M } wp-cron: image: wordpress:cli environment: *common_env secrets: [mysql_wp_password___DOMAIN__] networks: [privNet] working_dir: /var/www/html volumes: - type: bind source: /mnt/data/sites/__DOMAIN__/code target: /var/www/html command: ["bash","-lc","while true; do wp cron event run --due-now || true; sleep 60; done"] security_opt: ["no-new-privileges:true"] deploy: placement: { constraints: ["node.labels.pool == wp","node.role == worker"] } resources: limits: { cpus: "0.10", memory: 128M } reservations: { cpus: "0.05", memory: 64M }