WebP Cloud Services Blog

在 2024 年用 Docker-Compose 快速部署 WordPress 并把它藏在 Cloudflare 网络中,同时加入 WebP/AVIF 图片转换

大家可以看到我们的标题很长,分为如下几个部分:

  • 在 2024 年
  • 用 Docker-Compose 快速部署 WordPress
  • 把 WordPress 藏在 Cloudflare 网络中
  • 加入 WebP/AVIF 图片转换

本文旨在完成上面三个目标,让我们开始吧!

Docker 和 Docker Compose

是什么

以防有同学不太了解 Docker 和 Docker Compose 分别是什么,我们让 ChatGPT 生成了一段简短的介绍:

Docker 是一个开源的容器化平台,允许开发者自动化地部署、扩展和管理应用程序。

Docker-Compose 是一个用于定义和运行多容器 Docker 应用的工具。它主要用于在单个主机上管理多个相互依赖的容器。

简单来说,当你安装了 Docker 之后你就可以在机器上运行一些容器,比如可以运行一个 WebP Server Go 的容器,并且将机器上的 /path/to/pics 目录映射到容器中的 /opt/pics 目录下:

docker run -d -p 3333:3333 -v /path/to/pics:/opt/pics --name webp-server --restart always webpsh/webp-server-go 

但是这样运行的容器不好管理,每次启动或者重启都需要 docker start webp-serverdocker stop webp-server 之类,也不太好和别的有状态的容器互通,所以我们一般使用 Docker Compose 来管理容器,例如同样是完成上述事情我们可以写一个 docker-compose.yml 文件,内容如下:

version: '3'

services:
  webp-server:
    image: webpsh/webp-server-go
    restart: always
    volumes:
      - /path/to/pics:/opt/pics
    ports:
      - 3333:3333

这样我们可以很清晰地看出容器是什么,映射了什么目录,暴露了什么端口,此外,在包含这个 docker-compose.yml 文件的目录下我们可以通过 docker-compose up -d 启动,通过 docker-compose down 来停止这个服务,是不是很方便?

怎么安装

要在一个没有安装过 Docker 和 Docker Compose 的机器上安装,可以使用如下指令(如果你是用的 AMD64 架构的处理器):

curl -fsSL https://get.docker.com -o install-docker.sh && bash install-docker.sh
wget https://github.com/docker/compose/releases/download/v2.31.0/docker-compose-linux-x86_64 -O /usr/bin/docker-compose
chmod +x /usr/bin/docker-compose

如果你用的是 ARM64 的架构的机器(比如我们在用的 Hetzner ARM64),那么可以参考如下:

curl -fsSL https://get.docker.com -o install-docker.sh && bash install-docker.sh
wget https://github.com/docker/compose/releases/download/v2.31.0/docker-compose-linux-aarch64 -O /usr/bin/docker-compose
chmod +x /usr/bin/docker-compose

是的,我们 WebP Cloud 内部的机器就是这么安装的 Docker 和 Docker Compose

用 Docker Compose 快速部署 WordPress

想必上网时间比较久的同学可能对于老版本的 WordPress 安装还有印象,当时大家是这么安装的:

  • 购买一个 CPanel 主机
  • 下载 WordPress 的 zip 包并使用 Filezilla 上传到主机上
  • 在 CPanel 后台的文件管理器解压那个 zip 包
  • 浏览器访问自己的 IP/域名,开始配置 WordPress
  • 配置 CDN/域名啥的

到了 “VPS” 时代,大家的安装方式可能是参考网上的各个教程,一般来说有如下安装方式:

  • 买 VPS 主机
  • 在 VPS 上安装 Nginx/PHP-FPM,并配置 Nginx/PHP-FPM/MySQL
  • 下载 WordPress 的 zip 并解压到 /var/www 目录下
  • 继续配置 Nginx/PHP-FPM/MySQL,经过多次报错+重启,站点终于可以运行了
  • 配置 CDN/域名啥的

但这样还是很累,我们花了大量的时间配置 WordPress 的运行时环境,而且几乎所有人都得使用一样的配置。

我们来想一下 WordPress 需要什么:

  • PHP 运行时环境
  • Nginx/Apache 作为 Web 服务器
  • MySQL 作为数据库

想明白了这个点之后我们就可以直接用容器来替代上面繁琐的步骤了,幸运的是 WordPress 在 DockerHub 上有官方镜像,包含了 PHP 运行时环境和 Nginx/Apache 作为 Web 服务器,然后我们只需要找一个 MySQL 即可,于是我们有了如下 WordPress 快速可用的 Compose 文件 ,可以创建一个空的目录,并在目录中创建一个名为 docker-compose.yml 的文件:

version: '3'

services:
  wordpress:
    image: wordpress:latest
    restart: always
    volumes:
       - ./wordpress:/var/www/html
    ports:
       - 3000:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: root
      WORDPRESS_DB_PASSWORD: password
      WORDPRESS_DB_NAME: db

  db:
    image: ubuntu/mysql:8.0-22.04_beta
    restart: always
    environment:
      MYSQL_DATABASE: 'db'
      MYSQL_ROOT_PASSWORD: 'password'
    volumes:
      - ./db_data:/var/lib/mysql

我们来解释一下这里做了什么:

  • 我们声明需要创建两个容器,名字分别是 wordpressdb
  • wordpress 容器中的 80 端口映射到本机的 3000 端口(因为 wordpress 容器内的 WordPress 在 80 端口上)
  • WordPress 的数据会被放到 ./wordpress 目录下, MySQL 的数据会被放到 ./db_data 目录下

此时在这个目录下 docker-compose up -d 即可启动 WordPress 和 MySQL ,并且在 http://localhost:3000 可以访问到自己的 WordPress 了,我们第一个小目标「用 Docker Compose 快速部署 WordPress」已经完成!

  • 如果需要停止这两个容器就只需要在这个目录下 docker-compose down 即可
  • 如果需要把 WordPress 迁移走的话只需要将容器停止之后把整个目录迁移走,然后启动的话直接 docker-compose up -d 就可以了,没有任何外部文件依赖

把 WordPress 藏在 Cloudflare 网络中

http://localhost:3000 访问自己的 WordPress 肯定不行,我们得让所有人都能看到我们的 WordPress,以往许多教程会在这个之后教你「配置 Nginx 反向代理」,然后你通过大量的配置+DNS 解析之后终于可以在 http://xxx.xxx.xxx.xxx 访问你的站点了,为了给网站加上 https://,你听从了许多人的建议「使用 CloudFlare CDN」,创建了一个到你的 IP 的解析并开启了「Proxy」,嘿,https://your-domain.tld 就可以访问了,同时 your-domain.tld 使用了 Cloudflare 的 IP,攻击者没法直接看到你的服务器 IP。

但是这里又有一个问题,目前的部署下你的服务器 80/443 端口还是暴露在公网上(这样 Cloudflare 才能正确把流量转发到你的服务器上)的,理论上说是可以通过遍历 IP 的方式直接扫到你的机器 IP,并直接通过访问 IP 的方式找到你的网站 IP,如果这个时候有坏小子想 DDoS 你的话,你的站就又死了。

此时有人会建议在 Nginx 上配置一下只允许 Cloudflare IP 来访问你的网站,类似 nginx - How do I deny all requests not from cloudflare? - Server Fault 中的方式,然后你又一顿配置,终于解决了这个问题。

有没有更加简便的方式?

有,用 Cloudflare Tunnel(cloudflared),这个软件会在服务器上运行并建立对外到 Cloudflare 网络的连接(这样你的服务器就只需要暴露 SSH 端口就行,不需要暴露任何别的端口)。

可以参考「使用 Cloudflare Argo Tunnel(cloudflared) 来加速和保护你的网站」了解更多关于 Cloudflare Tunnel 的信息。

我们来试试看吧!

创建 Cloudflare Tunnel

在 CloudFlare 的 Zero Trust 面板下选择 「Network」下的「Tunnels」并选择「Cloudflared」

给这个 Tunnel 起一个名字(后期随时可以改)

在这个面板下你可以看到配置的 Token(那个 eyJhxxx 的部分)

由于我们要部署方便,且考虑可迁移性,我们 Cloudflare Tunnel 也会在容器中运行,运行方式如下:

version: '3'

services:
  cloudflared:
    image: cloudflare/cloudflared
    restart: always
    command: --no-autoupdate tunnel run
    environment:
      - TUNNEL_TOKEN=eyJhIjoi.....JMSJ9

和 WordPress 合并在一起

Cloudflare Tunnel 可以帮我将本地服务暴露,我们正好有个 WordPress ,所以我们需要将这个 cloudflared 和已经安装好的 WordPress 捏在一起,参考如下:

version: '3'

services:
  wordpress:
    image: wordpress:latest
    restart: always
    volumes:
       - ./wordpress:/var/www/html
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: root
      WORDPRESS_DB_PASSWORD: password
      WORDPRESS_DB_NAME: db

  db:
    image: ubuntu/mysql:8.0-22.04_beta
    restart: always
    environment:
      MYSQL_DATABASE: 'db'
      MYSQL_ROOT_PASSWORD: 'password'
    volumes:
      - ./db_data:/var/lib/mysql

  cloudflared:
    image: cloudflare/cloudflared
    restart: always
    command: --no-autoupdate tunnel run
    environment:
      - TUNNEL_TOKEN=eyJhIjoi.....JMSJ9

注意,这里我把 wordpress 端口暴露给删了,因为我们不再需要将 WordPress 容器中的端口暴露到本机了,只需要在这个 Compose 中可用即可。

如果你的机器不支持服务商的防火墙,而是使用 ufw 等工具拦截端口的话,Docker 的 3000:80 写法会因为 Docker 自身的 iptables 规则导致你的 3000 端口直接暴露在公网上,增加了攻击面。

Docker Compose 网络

这里需要插播一个小知识,像上文中的 Docker Compose 写法中,wordpressdbcloudflared 会在一个单独为他们创建的网络下,并且互相之间可以通过服务的名字联通,比如我可以在 wordpress 容器下 ping cloudflared 容器:

root@4fdc0a781999:/opt# ping cloudflared
PING cloudflared (192.168.160.4) 56(84) bytes of data.
64 bytes from wordpresswebpsh-cloudflared-1.wordpresswebpsh_default (192.168.160.4): icmp_seq=1 ttl=64 time=0.178 ms
64 bytes from wordpresswebpsh-cloudflared-1.wordpresswebpsh_default (192.168.160.4): icmp_seq=2 ttl=64 time=0.069 ms
64 bytes from wordpresswebpsh-cloudflared-1.wordpresswebpsh_default (192.168.160.4): icmp_seq=3 ttl=64 time=0.110 ms
^C
--- cloudflared ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2020ms
rtt min/avg/max/mdev = 0.069/0.119/0.178/0.044 ms

配置 Cloudflared 转发

有了上述预备知识之后我们就可以知道如何配置 Cloudflared 了,我们已知 WordPress 的地址是 wordpress(容器内),且端口是 80 端口,那我们就可以在 Cloudflare 上配置我们的规则,如下:

这里的意思是:

  • 如果来源请求是到达了 https://wordpress.webp.sh/*
  • 那么转发流量到 http://wordpress (默认是 80 端口)服务中

然后我们的 Demo 站就上线了,在这里: https://wordpress.webp.sh/

第二个目标「把 WordPress 藏在 Cloudflare 网络中」已经完成,我们来看看最后一个目标!

加入 WebP/AVIF 图片转换

在 WordPress 6.7 版本后, 终于支持了 HEIC 图片的上传,并在服务器中安装了 Imagick 的情况下自动将 HEIC 图片转换成 JPG 图片(如果没有安装的话那就仅仅是能上传 HEIC 图片而已),我们写了一篇文章记录这个事情「WordPress 6.7 终于支持 HEIC 图片了?谈谈 WebP Cloud 对于 HEIC 的支持」。

为什么需要加入 WebP/AVIF 图片转换

对于不同的用户而言,原始图片可能多种多样,有 JPG/PNG 等等,但这些原始格式的图片体积可能会比较大,如果不加以优化的话用户加载起来速度会比较慢,同时 Pagespeed 的数据会比较难看。所以这里我们需要一些手段可以将图片压缩或者转码成压缩率更高的图片格式。

我们对比一下通过 Google PageSpeed Insights 给出的建议:

在将图片转换成 WebP/AVIF 之前,我们发现 PageSpeed 建议「Serve images in next-gen formats」

在将图片转换成 WebP/AVIF 之后,这个建议就消失了,整体 PageSpeed 也提升了不少。

我们的追求是?

我们来整理一下我们的需求:

  • 要支持不同格式的原始图片(JPG/PNG/GIF/HEIC 至少)
  • 可以将图片转换成较为现代,压缩率更高的格式,例如 WebP/AVIF/JPEG XL 等
  • 尽量得无缝整合到 WordPress 中
  • 可以针对不同的浏览器可以输出不同格式的图片(例如我们肯定不能给一个不支持 AVIF 的浏览器输出 AVIF 格式的图片,那样用户就根本看不到图片了)
  • 如果能针对不同分辨率的屏幕输出不同尺寸的图片就更好了(不过这一点在 WordPress 上 WordPress 已经通过将原始图片转换成不同尺寸保存来间接做到了)

在 WebP Cloud Services ,我们专攻这个方面的需求,在这里有两个选择:

由于本文主要介绍的是自托管的模式,所以这里只介绍 WebP Server Go 的使用方法。

WebP Server Go 是我们在 2021 年时做出来的开源产品,GitHub 地址是 https://github.com/webp-sh/webp_server_go (来给我们点个 Star?),旨在提供一个无缝的将图片从 JPG/PNG 等格式转换成 WebP/AVIF 格式的工具,并保持 URL 不变。

例如你的图片在服务器上的目录是 /path/to/image.png ,并且对外是通过 https://your-server.tld/to/image.png 访问的话,启动 WebP Server Go 之后便可以继续用 https://your-server.tld/to/image.png 访问图片,并且得到的是体积更小的 WebP/AVIF 格式,此外还会保证老旧的浏览器(或者 cURL)依然可以展示原始的图片保证兼容性。

我们知道 WordPress 存储用户上传的路径是在 /wp-content/uploads/ 下,例如我上传的一张图片:

https://wordpress.webp.sh/wp-content/uploads/2024/11/ccc.jpg

WebP Server Go Kicks in!

只需要将 ./wordpress 目录挂载到 WebP Server 下即可,我们继续编辑 docker-compose.yml 文件,加入 WebP Server 的部分,现在这个文件看起来是这样:

version: '3'

services:
  wordpress:
    image: wordpress:latest
    restart: always
    volumes:
       - ./wordpress:/var/www/html
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: root
      WORDPRESS_DB_PASSWORD: password
      WORDPRESS_DB_NAME: db

  db:
    image: ubuntu/mysql:8.0-22.04_beta
    restart: always
    environment:
      MYSQL_DATABASE: 'db'
      MYSQL_ROOT_PASSWORD: 'password'
    volumes:
      - ./db_data:/var/lib/mysql

  cloudflared:
    image: cloudflare/cloudflared
    restart: always
    command: --no-autoupdate tunnel run
    environment:
      - TUNNEL_TOKEN=eyJhIjoi.....JMSJ9

  webpserver:
    image: ghcr.io/webp-sh/webp_server_go
    restart: always
    volumes:
      - ./wordpress:/opt/pics

通过 docker-compose up -d 启动后,WebP Server 默认便会监听到容器中的 3333 端口上了,关于更多的参数和配置信息,可以参考我们的文档: https://docs.webp.sh/usage/configuration/

然后,我们需要将 /wp-content/uploads/ 下的图片请求转发到 webpserver 上,这样才能让 WebP Server 处理图片请求,于是我们配置一下 Cloudflare Tunnel:

将图片请求转发给 WebP Server 处理

记得调整一下优先级,保证图片请求会被优先匹配到 WebP Server 上(把这个规则放到第一个):

此时我们再来访问这个图片就会发现已经是输出了 WebP 格式的图片了,体积也缩小到了原图的 82%!

哦对了,WebP Server 是支持 HEIC 作为原图的,我们来试试看:

我们上传了一张 HEIC 图片,地址是 https://wordpress.webp.sh/wp-content/uploads/2024/11/sample3.heic,上传之后 WordPress 自动帮我们将图片转换成了 JPG 格式,给出的地址是:https://wordpress.webp.sh/wp-content/uploads/2024/11/sample3.jpg

将 HEIC 转换成 JPG 的原因可以参考 「WordPress 6.7 终于支持 HEIC 图片了?谈谈 WebP Cloud 对于 HEIC 的支持

此时访问给出的地址可以看到被 WebP Server 优化后的图片(从 JPG -> WebP):

https://wordpress.webp.sh/wp-content/uploads/2024/11/sample3.jpg

或者如果你想直接访问 HEIC 图片也可以:

https://wordpress.webp.sh/wp-content/uploads/2024/11/sample3.heic

不过这里目前还有个小问题,这张图片被转换成 WebP 之后发现 WebP 图片尺寸大于原始 HEIC 图片,所以 WebP Server 选择了渲染 HEIC 图片,而这里我们的浏览器还不支持渲染 HEIC 的话就看不到图片了,我们会在下个版本中修复这个问题,可以关注我们的 PR:https://github.com/webp-sh/webp_server_go/pull/367

给图片请求关闭缓存

这里其实有一点 Tradeoff 的意思在,当你使用 Cloudflare 的时候,Cloudflare 默认会缓存你的图片(仅在访客访问到的 Datacenter 上,且缓存时间取决于这个资源被访问的是否频繁),这个特性对于你的图片就是简单的图片而言很有用,更多关于 Cloudflare 缓存的介绍可以参考我们的「WebP Cloud 和 Cloudflare Polish 对比」一文。

但是如果使用 WebP Server 的话,我们会针对不同的浏览器输出不同格式的图片(比如我们会给支持 WebP 格式的浏览器输出 WebP 格式的图片,给不支持 WebP 格式的浏览器(当然,在 2024 年的当下已经比较少见了)输出原图),而 Cloudflare 缓存会随机缓存其中一个格式:

例如第一个访客访问的时候得到了 WebP 格式的图片,并被 Cloudflare 缓存了,那第二个访问到同一个 Datacenter 的访客会得到被 Cloudflare 缓存过的 WebP 图片,如果这第二个访客的浏览器不支持 WebP 格式的话,图片就完全显示不了了。

在这个情况下我们需要给图片的请求关闭缓存,也很方便,只要配置一个「Cache Rule」就行,配置类似如下:

我们最后请求一下这个图片确认一下是不是真的已经跳过缓存了,这里我们用 cURL 来模拟请求:

curl -H "Accept: image/webp,image/avif" https://wordpress.webp.sh/wp-content/uploads/2024/11/ccc.jpg -I
HTTP/2 200 
...
content-type: image/webp
content-length: 115038
...
x-compression-rate: 0.82
cf-cache-status: DYNAMIC
....

cf-cache-status: DYNAMIC 可以知道,这个请求已经不会被 Cloudflare 缓存。

恭喜,我们最后一个目标也完成了!


WebP Cloud Services 团队是一个来自上海和赫尔辛堡的三人小团队,由于我们不融资,且没有盈利压力 ,所以我们会坚持做我们认为正确的事情,力求在我们的资源和能力允许范围内尽量把事情做到最好, 同时也会在不影响对外提供的服务的情况下整更多的活,并在我们产品上实践各种新奇的东西。

如果你觉得我们的这个服务有意思或者对我们服务感兴趣,欢迎登录 WebP Cloud Dashboard 来体验,如果你好奇它还有哪些神奇的功能,可以来看看我们的文档 WebP Cloud Services Docs,希望大家玩的开心~


Discuss on Hacker News