3

WordPress SQLite Docker 镜像封装细节

 3 weeks ago
source link: https://soulteary.com/2024/04/21/wordpress-sqlite-docker-image-packaging-details.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

WordPress SQLite Docker 镜像封装细节

2024年04月21日阅读Markdown格式4919字10分钟阅读

为了让大家用的放心,同时解答 GitHub 社区中的疑问。这篇文章聊聊上一篇文章的 Docker 容器封装细节。

在前一篇文章《WordPress 告别 MySQL:Docker SQLite WordPress》中,如果你跟着文章实践,大概三分钟就能够启动一个不需要 “数据库” 的 WordPress 的容器实例。(毕竟许多人不认为 SQLite 不是数据库嘛)

为了让大家使用的放心,减少使用中对于“黑盒”的顾虑(明明都开源啦),本篇文章聊聊这个技术方案背后的细节,以及简单聊聊如何使用 API 对其进行数据交互。

项目的开源代码仓库soulteary/docker-sqlite-wordpress中,其中核心实现是这个看起来复杂实际很简单的 Dockerfile 文件:

FROM wordpress:6.5.2-php8.3-apache
LABEL org.opencontainers.image.authors="[email protected]"

SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ENV WORDPRESS_PREPARE_DIR=/usr/src/wordpress

# plugin: https://github.com/WordPress/sqlite-database-integration
ENV SQLITE_DATABASE_INTEGRATION_VERSION=2.1.9
RUN curl -L -o sqlite-database-integration.tar.gz "https://github.com/WordPress/sqlite-database-integration/archive/refs/tags/v${SQLITE_DATABASE_INTEGRATION_VERSION}.tar.gz" && \
    tar zxvf sqlite-database-integration.tar.gz && \
    mkdir -p ${WORDPRESS_PREPARE_DIR}/wp-content/mu-plugins/sqlite-database-integration && \
    cp -r sqlite-database-integration-${SQLITE_DATABASE_INTEGRATION_VERSION}/* ${WORDPRESS_PREPARE_DIR}/wp-content/mu-plugins/sqlite-database-integration/ && \
    rm -rf sqlite-database-integration-${SQLITE_DATABASE_INTEGRATION_VERSION} && \
    rm -rf sqlite-database-integration.tar.gz && \
    mv "${WORDPRESS_PREPARE_DIR}/wp-content/mu-plugins/sqlite-database-integration/db.copy" "${WORDPRESS_PREPARE_DIR}/wp-content/db.php" && \
    sed -i 's#{SQLITE_IMPLEMENTATION_FOLDER_PATH}#/var/www/html/wp-content/mu-plugins/sqlite-database-integration#' "${WORDPRESS_PREPARE_DIR}/wp-content/db.php" && \
    sed -i 's#{SQLITE_PLUGIN}#sqlite-database-integration/load.php#' "${WORDPRESS_PREPARE_DIR}/wp-content/db.php" && \
    mkdir "${WORDPRESS_PREPARE_DIR}/wp-content/database" && \
    touch "${WORDPRESS_PREPARE_DIR}/wp-content/database/.ht.sqlite" && \
    chmod 640 "${WORDPRESS_PREPARE_DIR}/wp-content/database/.ht.sqlite"

上面的 Dockerfile 中,主要做了下面几件事:

  1. 基于官方镜像的某个指定版本进行构建,能够更快的滚动更新,而非从零到一构建。(例子中是 wordpress:6.5.2-php8.3-apache
  2. 将容器的 SHELL 环境切换为 BASH,让接下来的命令行为在各种不同架构中保持一致,书写起来也更方便。
  3. 基于官方 SQLite 项目进行项目的初始化,下载程序压缩包并解压缩到指定位置,将插件放置到默认激活的 mu-plugins 目录,将程序文件放置到 wp-content/db.php 替换默认数据库对象,准备一个默认的数据目录 wp-content/database,避免初始化遇到权限问题。

好了,当我们清楚了解所有的主要动作之后,让我们来了解一些重要细节。这些细节涉及到了为何官方没有默认支持这个能力,以及如果你想自己定制或改进,可能踩到的坑。

为什么要基于官方镜像进行构建

下载量巨大的官方镜像

下载量巨大的官方镜像

之所以基于官方镜像进行更新,而非完全从零到一进行构建,有几个好处:

  1. 引入的安全风险最低,只是添加了一个新的“数据层”的选项,没有改动任何系统运行环境、其他程序文件都是官方发布镜像中的。目前我们从 DockerHub 下载的 WordPress 镜像已经拥有超过 10亿 的下载量,是被重点维护和保护的项目,上下游供应链相对可靠。
  2. 维护成本最低,因为和官方镜像差异小,我可以主要关注上游项目WordPress/sqlite-database-integration的变化和 WordPress 的变化,而不需要关注更多的诸如运行环境 Linux 镜像内部、语言运行时 Runtime 的变化。
  3. 发布简单透明,我创建了一个 GitHub 自动化发布配置(docker-sqlite-wordpress/.github/workflows/build-version.yaml),当官方发布新版本后,我只需要更新 Dockerfile 中的版本号,点击发布按钮,干净的 GitHub 构建环境就会开始组装新的镜像,并推送到 Docker Hub,用户就能够下载使用啦。

除此之外,需要折腾的事情都有点多,不利于为爱发电的项目。

为什么没有初始化程序到 /var/www/html 目录

如果你仔细阅读 Dockerfile,你将会看到一个有趣的设置:

ENV WORDPRESS_PREPARE_DIR=/usr/src/wordpress

我们将程序放置在了一个 “准备目录”(PREPARE_DIR),而非实际的运行目录 /var/www/html,这是因为 WordPress 官方镜像默认包含一个“入口程序”(docker-library/wordpress/docker-entrypoint.sh),它有一些有趣的行为,导致我们直接操作程序最终运行的 /var/www/html 目录,会出现数据覆盖的问题。

...
for contentPath in \
    /usr/src/wordpress/.htaccess \
    /usr/src/wordpress/wp-content/*/*/ \
; do
    contentPath="${contentPath%/}"
    [ -e "$contentPath" ] || continue
    contentPath="${contentPath#/usr/src/wordpress/}" # "wp-content/plugins/akismet", etc.
    if [ -e "$PWD/$contentPath" ]; then
        echo >&2 "WARNING: '$PWD/$contentPath' exists! (not copying the WordPress version)"
        sourceTarArgs+=( --exclude "./$contentPath" )
    fi
done
tar "${sourceTarArgs[@]}" . | tar "${targetTarArgs[@]}"
echo >&2 "Complete! WordPress has been successfully copied to $PWD"
...

所以,为了避免这个问题,初始化插件的目录,要选择解决“源头”。

使用 wp-content/mu-plugins 而非 wp-content/plugins

Must Use Plugins” 指的是一种特殊的插件,如果我们为用户提供一些特殊的 WordPress 能力,不希望用户进行禁用,会使用的方案。十年前维护 WordPress For SAE的时候,折腾过这个方案。

在这个场景下,我们选择在 mu-plugins 目录放置必须启用插件,而没有使用普通 plugins 目录,出于两个考虑。

  1. 如果 SQLite 这类数据库选择是在程序安装时该被解决的,那么程序应当在用户初始化安装的时候就可以使用。如果放置 plugins 目录,用户需要先启动一个 MySQL 数据库,把 WordPress 安装完毕后,再进行插件激活,然后再进行数据迁移,这样未免太麻烦了。
  2. plugins 目录的插件是可以被删除的,如果我们使用 SQLite 存储数据,但是用户恰好好奇心泛滥,在插件管理页面点击了“删除”插件,虽然网站数据不会有损失,但是网站就无法正常运行啦。

其他:数据库文件的安全

有知乎的网友评论中担忧,这里是否存在十多年前许多网站使用 Access 数据库,因为暴露网站数据库文件路径,被有心人下载数据库文件,导致数据被拖库的问题。

其实并不会,注意到文件命名中的 .ht 开头的命名了吗?在开源软件 Apache (Docker WordPress 镜像默认服务器)的配置中:

#
# The following lines prevent .htaccess and .htpasswd files from being
# viewed by Web clients.
#
<FilesMatch "^\.ht">
	Require all denied
</FilesMatch>

所以,你访问数据库路径,比如:http://localhost:8080/wp-content/database/.ht.sqlite,将得到禁止访问的结果。

哪怕你和我一样懒,就使用默认数据库名称

哪怕你和我一样懒,就使用默认数据库名称

当然,你也可以自己定义新的数据库目录和文件名称,在官方插件的逻辑中有这么一段为懒人兜底的功能实现:

/**
 * Notice:
 * Your scripts have the permission to create directories or files on your server.
 * If you write in your wp-config.php like below, we take these definitions.
 * define('DB_DIR', '/full_path_to_the_database_directory/');
 * define('DB_FILE', 'database_file_name');
 */

/**
 * FQDBDIR is a directory where the sqlite database file is placed.
 * If DB_DIR is defined, it is used as FQDBDIR.
 */
if ( ! defined( 'FQDBDIR' ) ) {
	if ( defined( 'DB_DIR' ) ) {
		define( 'FQDBDIR', trailingslashit( DB_DIR ) );
	} elseif ( defined( 'WP_CONTENT_DIR' ) ) {
		define( 'FQDBDIR', WP_CONTENT_DIR . '/database/' );
	} else {
		define( 'FQDBDIR', ABSPATH . 'wp-content/database/' );
	}
}

/**
 * FQDB is a database file name. If DB_FILE is defined, it is used
 * as FQDB.
 */
if ( ! defined( 'FQDB' ) ) {
	if ( defined( 'DB_FILE' ) ) {
		define( 'FQDB', FQDBDIR . DB_FILE );
	} else {
		define( 'FQDB', FQDBDIR . '.ht.sqlite' );
	}
}

所以,我们只需要在配置中定义下面的变量数值,即可改变数据库的位置:

define( 'FQDBDIR', 'somewhere/for/database/save/directory');
define( 'DB_FILE', 'lalala.demaxiya');

这篇文章,就先聊到这里,下一篇相关的内容,我们聊聊这个方案的 API 调用方案,怎么拿它当 CMS 使用。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK