网上关于用自己的 VPS 搭博客的教程非常多,但是多半是一些非常陈旧和杂乱的实践。
作为一个折腾博客五年多的「折腾达人」,本文汇总自己多年的折腾经验,形成一个我认为的「最佳实践」,希望能够作为参考。同时为了简化过程,这篇博客介绍的并不完全是我服务器上的部署方式,更加优化的一些地方在文末有列举。
💡 这篇博客介绍的「最佳实践」的含义为「已经在别处产生显著效果并且能够适用于此处的优秀实践」,而不是「最优秀的实践」,并无「最」字面上「达到极点」的含义。无意否认存在比这篇博客的介绍更加简便、清晰、强大、优秀的实践。
前置知识
- Linux 终端(Shell)的基本使用。
- 容器化与 Docker 的相关概念。
- Docker 容器(Container)、镜像(Image)、网络(Network)的相关概念。
- Docker Compose 以及 YAML 基本语法。
- MySQL 数据库的相关概念。
部署环境架构的选择
如果用自己的 VPS 搭建博客,部署环境有几种常见的方式:
- LNMP:Linux + Nginx + MySQL + PHP,是常见的传统架构。这种方式维护起来非常麻烦,虽然有类似「XX 面板」、「XX 一键安装包」之类的管理器 / 脚本,但是这种近乎「黑箱」的运维方式是不利于我们的学习研究的。
- 容器化,即全部使用 Docker 之类的容器工具进行部署。容器化对于我们博客部署和个人服务器运维的最大好处就是维护非常方便。
容器化是近年的趋势,也是这篇博客介绍的方法。
博客系统的选择
目前开源的博客系统五花八门,我也尝试过很多。从整体的架构上可以分为三类:
- SSR(Server-side Render,服务器端渲染),就是各种 PHP、JSP 博客系统,代表为 WordPress、Typecho。这类博客平台最为成熟。
- Prerender(预渲染),就是一些静态博客生成器,代表为 Hexo、Hugo 等。这种博客系统可以部署在 Github Pages 这样的平台上,不需要自己的数据库,但是是纯静态的,诸如评论区这样的功能只能依赖第三方嵌入。
- CSR(Client-side Render,客户端渲染),就是前后端分离的架构,代表有 Ghost 等。
我个人比较推荐的是 WordPress 或者 Typecho。
WordPress
WordPress 是一个十分成熟的 PHP 博客程序。据统计,整个互联网约有三分之一的网站使用 WordPress。
新版本的 WordPress 内置的是 Gutenberg 编辑器,这是一个强大的“块编辑器”(区别于传统的文字编辑器)。这个编辑器比较现代化,对用户友好,但是实现比较复杂。如果你习惯使用 Markdown 来写文章,或者想要对博客系统进行一些二次开发,可能会比较麻烦,更推荐使用 Typecho。
💡 **注意:**开源的 WordPress 博客程序项目的官网是 wordpress.org,而不是 wordpress.com!后者是 WordPress 的公司 Automatic 运营的线上博客服务,和前者没有直接关系。
Typecho
Typecho 是国人开发的 PHP 博客系统,非常简洁轻量,比 WordPress 更加轻量。Typecho 隔几年才更新一次,已经有十五年以上的历史了……我的这个博客就是用 Typecho 搭建的。
Typecho 原生支持 Markdown,而且主题、插件的开发都比 WordPress 要容易地多。不过对于对 Markdown 不熟悉的用户来说,这个编辑器比 WordPress「简陋」地多。
总结一句,**WordPress 强大全面,Typecho 轻量简洁,**看个人喜好选择。
这篇博客以 Typecho 为例。
安装 Docker 和 Compose
Docker 是最常用的容器平台。
首先我们要在服务器上安装 Docker(注意是安装 Docker Engine 不是 Docker Desktop),然后安装 Docker Compose。可以参考官方的安装文档。
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
Docker Compose 的安装文档在这里。
架构规划
我们需要运行以下几个容器:
- Typecho-fpm,是部署好的 PHP 环境 + Typecho。
- MariaDB 或 MySQL,数据库。
- phpMyAdmin,是管理 MySQL 数据库用的软件,有了这个我们就不用自己用数据库命令去创建管理数据库了(当然如果你比较熟悉 SQL 可以不用这个)。
还需要安装 Caddy(或 Nginx),作为 Web Server。这个相当于作为整个服务器的「网关」,可以不以容器的方式安装(因为 Caddy 的 caddy reload
命令真的很爽)。
创建容器网络
为了让 Typecho 容器能够访问数据库,phpMyAdmin 能够管理数据库,我们要分别为他们创建网络,和数据库互联。假定这两个网络名为 blog
和 db_admin
。
我们可以先创建网络,稍后在相关的 docker-compose.yml
中指定连接:
docker network create blog
docker network create db_admin
安装启动数据库
Typecho 支持 MySQL 数据库。此处我们选用 MariaDB。
💡 **关于 MariaDB 与 MySQL:**MariaDB 是 MySQL 关系数据库管理系统的一个 Fork,由社区开发,有商业支持,旨在继续保持在 GNU GPL 下开源。MariaDB 打算保持与 MySQL 的高度兼容性,确保具有库二进制奇偶校验的直接替换功能,以及与 MySQL API 和命令的精确匹配。感觉二者的关系如同 Rocky Linux 与 CentOS。我个人比较推荐使用前者。
为了维护的方便,这篇博客介绍的容器运行均使用 Docker Compose。运行数据库的 docker-compose.yml
如下:
version: "3"
services:
mariadb:
image: mariadb:latest
container_name: mariadb
networks:
- blog
- db_admin
environment:
- MARIADB_ROOT_PASSWORD=NhwaJU0tW7UZ5u
volumes:
- /data/mysql:/var/lib/mysql
restart: unless-stopped
networks:
blog:
external: true
db_admin:
external: true
environment
(环境变量)中MARIADB_ROOT_PASSWORD
指定了数据库root
用户的密码,强烈建议修改为一个随机的字符串。volumes
(卷)中的内容指定将容器内/var/lib/mysql
路径挂载到宿主机里/data/mysql
位置,也就是将数据库存储的内容挂载出来。这样,即使日后删除了容器,重新按此配置启动,数据库仍然能正常加载原来的数据,不会导致数据丢失。这就是「容器无状态」的理念。
将文件命名为 docker-compose.yml
,放到一个单独的文件夹内。在这个文件夹内运行:
docker-compose up -d
Docker Compose 会自动 pull 最新的 MariaDB 镜像并在后台启动容器(-d
就是使容器后台运行)。使用命令 docker ps -a
查看所有容器,可以查看到该容器正在运行。
**💡 Docker 镜像的来源:**所有 Docker 镜像都可以在 Dockerhub 上找到,docker pull
的默认来源也是 Dockerhub。
💡 运行 docker-compose down
会停止并删除容器。所以我们需要「容器无状态」,将带有状态的数据都可持久化存储,挂载到外部。
用 phpMyAdmin 管理数据库
**💡 可以直接用 SQL:**如果你比较熟悉 SQL,可以直接用 docker exec -it mariadb /bin/sh
进入容器内部,用 SQL 操作数据库。phpMyAdmin 本质上只是封装了各种 SQL 语句的一个 Web 界面。
为了方便管理我们的数据库,我们再运行一个 phpMyAdmin 容器。同样写一个 docker-compose.yml
:
version: "3"
services:
phpmyadmin:
image: phpmyadmin/phpmyadmin:latest
container_name: phpmyadmin
environment:
- PMA_HOST=mariadb
networks:
- db_admin
ports:
- "8080:80"
restart: unless-stopped
networks:
db_admin:
external: true
environment
中PMA_HOST
指定数据库的主机名。由于数据库容器名为 mariadb,在 db_admin 这个网络中它的主机名就是 mariadb。ports
指定将容器内 80 端口映射到容器外 8080 端口。启动容器之后访问 http://IP:8080 就可以看到 phpMyAdmin 管理面板啦。
在 phpMyAdmin 中,我们需要为 Typecho 创建数据库和用户。点击上面的「账户」,然后点击「新增用户账户」,用户名填写 typecho,密码可以随机设置一个,并勾选「创建与用户同名的数据库并授予所有权限」。其他部分保持默认即可。
点击「执行」,我们就为 Typecho 创建了用户和数据库。
**💡 为什么不使用 root 用户?**root 用户拥有最高权限,任何时候使用 root 用户都是非常危险的。而我们创建的 typecho 用户则只有操作 typecho 这一个数据库的权限。
安装 Typecho(FPM)容器
💡 关于 typecho 与 typecho-fpm: 在 Dockerhub 上可以看到 Typecho 镜像有多个 tags:typecho
、typecho-apache
(同 typecho
)和 typecho-fpm
。typecho
镜像中自带了一个 Apache,启动容器后可以直接访问;而 typecho-fpm
只有 php 环境,并不带 Web 服务器,需要自己配置一个支持 php-fpm 的服务器提供访问。
为了使用 HTTPS,我们必须在外部配置一个 Web 服务器,所以如果使用前者,流量会经过两个 Web 服务器而拖慢速度。更推荐使用后者。WordPress 和 WordPress-fpm 镜像同理。原理图如下:
用以下 docker-compose.yml
文件启动 Typecho-fpm 容器:
version: "3"
services:
typecho:
image: joyqi/typecho:1.2.0-php8.0-fpm
container_name: typecho
networks:
- blog
volumes:
- "/data/typecho:/app"
ports:
- "9000:9000"
restart: unless-stopped
depends_on:
- "mariadb"
networks:
blog:
external: true
volumes
将网站数据挂载到/data/typecho
,这就是网站的根目录。ports
将 php 解析器的 9000 端口暴露出来,供容器外的 Caddy 使用。启动容器后,直接访问 http://IP:9000 会显示Bad request
。
安装 Caddy 与配置
为了方便使用,对于 Caddy,我们可以不用容器的安装方式,而是采用官方文档的方法直接安装。
以 Ubuntu / Debian 为例:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
安装之后,我们创建一个名为 Caddyfile 的文件,在其中写如下内容:
example.com {
root * /data/typecho
file_server
php_fastcgi 127.0.0.1:9000 {
root /app
}
}
-
第一行是网站的域名。注意域名之后是一个空格,再加花括号。
-
root
指定网站文件的根目录,这个是提供静态资源访问用的。 -
php_fastcgi
表示将.php
的请求交给 php 解析器,这个解析器的入口就是 127.0.0.1:9000。- 其中的
root
指定的是容器内部的网站根目录。由于我们将/app
挂载到了/data/typecho
,所以容器内外的root
是不一样的。
- 其中的
保存 Caddyfile 后,启动 Caddy(service caddy start
),在相同目录下 caddy reload
。这时访问 example.com 就可以看到 Typecho 的初始化界面了!
Typecho 初始化
数据库地址就是 mariadb 容器名,用户名、数据库名、密码就是刚才在 phpMyAdmin 里创建的。一切就绪后,点击「确认,开始安装」。
然后设置好 Typecho 登录用的账号密码,不出意外,Typecho 博客就搭建完成啦!
下一步
访问 https://你的域名/admin
进入 Typecho 的后台,可以定制主题、插件等等博客的一切。
访问 Typecho 官方文档了解更多信息。
其他可选的优化
-
我的服务器上的 Caddy 也使用容器的方式部署。我使用的是 Caddy-docker-proxy,这个 Caddy 附加的插件有 Docker 服务探测的能力,在
docker-compose.yml
中写若干labels
就可以自动反向代理。另一个好处是不需要暴露端口。 -
如果 Caddy 不使用容器安装,强烈建议给服务器装上防火墙,因为将 Typecho 容器的 9000 端口暴露出来是非常危险的。防火墙推荐使用 RedHat 的 firewalld,与 Docker 集成。Ubuntu 的 UFW 是不兼容 Docker 的。
-
Caddyfile 中,花括号内的配置可以加上一行:
header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
这样网站的安全性才能符合 HSTS Preload List 的要求。可以前往 hstspreload.org 提交自己的网站。
在互联网环境逐渐恶化的今天,自己的博客就是一片属于自己的天地。希望我们都能在自己的精神花园里畅所欲言。