---
title: 在Daocloud上部署Typecho
date: 2016-08-23T17:40:00+08:00
tags:
  - Docker
  - Typecho
  - Web
  - 日记

---

大概是几个月前我在 v2ex 上看到了 [DaoCloud](https://www.daocloud.io/) 的宣传，开始接触到了 Docker 这个神奇的容器引擎和 DaoCloud 这个基于 Docker 技术的云平台

类似于将货物打包在集装箱上供远洋货轮运输的方式，我们可以将自己想要的程序及其运行环境打包成 Docker 镜像，然后把镜像上传到服务器上运行。采用 Docker 部署应用的一个优势是你可以根据自己的需要灵活地配置你想要的环境，最后只需要把一个 Docker 镜像交给服务器就能运行了。至于为什么要这么做呢，这里说的挺清楚的 >>>>>>[什么是 Docker？](http://docs.daocloud.io/faq/what-is-docker)

![768380369.png](https://cdn.zgq.me/assets/posts/2016/daocloud-typecho-deployment/768380369.png)

~~DaoCloud每个账户都有2x的免费配额（每个x分别代表着128M内存的服务器资源或者10G的存储空间），没有流量、运行时间之类的限制~~，而且速度挺不错，十分良心。于是我开始琢磨着看看能不能把这个blog迁移到 DaoCloud 上，迁移遇到了一些坑，把坑都填了之后用起来感觉还是挺不错的。在这里分享一下我折腾的过程。

首先是数据库的迁移，DaoCloud 可以免费创建一个 50M 的数据库，对于一个博客来说是绰绰有余（目前我这个2014年开的blog的数据库导出的sql文件也就200多k），DaoCloud 自带一个 phpmyadmin ，所以导入过程和一般的主机没什么区别，这里不再赘述。

其次是运行环境的配置，在 DaoCloud 中所有的应用都是以 Docker 镜像的方式发布的，所以我们需要解决的问题的就是如何构建一个我们想要的镜像和如何部署这个镜像。

DaoCloud 还有另一个特点，那就是在镜像重新部署之后，应用之前在运行时对自身容器的所有改动都将会丢失，直接变回镜像构建出来的状态，也就是说，如果我制作了一个 Typecho 的 Docker 镜像，网站运行时创建的配置文件，上传的图片等等在重新部署之后都会丢失，这显然不符合要求。

为此，DaoCloud 同时也提供了 Volume 服务，这里的 Volume 是“数据卷”的意思，顾名思义，它相当于一个外接的磁盘，它可以挂载到 Docker 容器中的某一个目录上，应用运行时对这个目录的更改直接对应于对这个磁盘的操作，弥补了容器重新部署时改动不能保存的弊端。

所以我最终选择的方案是，将 Typecho 所需要的 PHP 环境做成 Docker 镜像，Typecho 的所有文件都放在一个 Volume 里面，应用运行的时候再把这个 Volume 挂载到 Docker 容器的 /var/www/html 目录上（虚拟主机的既视感），创建一个 128M 内存的应用和一个 10G 的 Volume，正好消耗完了两个x的免费配额。

迁移网站之前得手动修改一下网站的 config.php ，把 MySQL 数据库的连接信息改成由 Daocloud 的环境变量获取。原本我用的是 MySQL 扩展，看到PHP新版本已经被抛弃 MySQL 扩展了，干脆换成 Pdo_Mysql 吧，修改后的代码如下

```php
/** 定义数据库参数 */
$db = new Typecho_Db('Pdo_Mysql', 'typecho_');
$db->addServer(array (
  'host' => getenv('MYSQL_PORT_3306_TCP_ADDR'),
  'user' => getenv('MYSQL_USERNAME'),
  'password' => getenv('MYSQL_PASSWORD'),
  'charset' => 'utf8',
  'port' => '3306',
  'database' => getenv('MYSQL_INSTANCE_NAME'),
), Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set($db);
```

然后把整个网站的程序和数据都传到Volume上，这个过程还是很顺利的。

接下来开始配置能运行 Typecho 的 PHP 环境的 Docker 镜像，这里需要用到 Dockerfile 来构建镜像。（[关于Dockerfile的介绍](http://docs.daocloud.io/ci-image-build/dockerfile)）

这里我选择了官方的 PHP Apache镜像为基础镜像

```docker
FROM daocloud.io/php:5.6-apache
```

默认的 PHP 镜像很多扩展都没有，所以我们得把要用到的扩展装上去

```docker
# docker-php-ext-install 为官方 PHP 镜像内置命令，用于安装 PHP 扩展依赖
RUN apt-get update && apt-get install -y php5-curl php5-gd
# pdo_mysql 为 PHP 连接 MySQL 扩展
RUN docker-php-ext-install pdo_mysql
```

为了支持 rewrite（主要用来支持 `.htaccess` 隐藏 `index.php`），加上这一行

```docker
RUN a2enmod rewrite
```

到目前为止构建的镜像已经可以运行了，看起来也没什么异常，但很快我便发现，网站中的主题的文件无法在 Typecho 后台在线修改、附件无法上传，根据我的推断应该是容器中运行的 PHP 进程没有操作 Volume 里面的文件的权限。

用 DaoCloud 送的代金券开了个专业版之后，我开了个终端进入正在运行的应用，定位到 `/var/www/html` 再 `ls -al` 发现，里面的文件所有者和组全都是 root ，难怪为什么 PHP 进程会无权操作。我试着执行 `chown -R www-data:www-data /var/www/html/` ，将里面的文件所有者和组变回了 `www-data` ，文件写入恢复正常。

经过一番测试我发现了一个规律，每次往 Volume 添加的文件的所有者默认都是 `root`，而 LAMP 的架构的网站运行的时候为了保证文件写入，所有者应该是 `www-data`。针对这个问题，我现在要做的是让它自己一开始就把 `/var/www/html/` 里面所有的文件的所有者都改成 `www-data`。

开始我在 Dockerfile 加入一行 `RUN chown -R www-data:www-data /var/www/html/` ，再试，发现并没有什么变化。仔细想想，Volume 是在镜像构建好之后部署的时候才绑定的，所以我在构建的时候更改目录的所有者并没有什么用。

于是我换了个思路，把这个 `chown -R www-data:www-data /var/www/html/` 放到了镜像的启动命令里面。结果是网页都打不开了。但我进入控制台定位到 `/var/www/html` 的时候发现文件所有者的确是变化了，说明这个命令还是起作用了，但这个命令执行的时候应该还是丢失了什么。

我开始查找关于 Dockerfile 的 CMD 指令的资料，发现这么一句：

> Dockerfile 只允许使用一次 CMD 指令。 使用多个 CMD 会抵消之前所有的指令，只有最后一个指令生效。
> 

但我这个 Dockerfile 之前并没有用过 CMD 指令，莫非是这个指令把基础镜像的 CMD 指令给抵消了？！带着这个问题，我找到了基础镜像的 Dockerfile >>> [基础镜像的Dockerfile的代码](https://github.com/docker-library/php/blob/11c5d11f1da7033ab3e3c0c1eb4442b57618a9cd/5.6/apache/Dockerfile)

发现它的启动命令

```docker
CMD ["apache2-foreground"]
```

根据我的猜测应该是后来的 CMD 指令覆盖了这个。于是我试着把 `apache2-foreground` 也写了进去，由于 CMD 只能运行一条命令，所以我把这两个指令写到了一个 sh 文件里面，这里命名为 `start.sh` ，具体内容如下：

```bash
#!/bin/bash
set -e
chown -R www-data:www-data /var/www/html/
apache2-foreground
```

然后在 Dockerfile 添加的内容（这里我直接把 `start.sh` 放到了 `/var/www` 目录下运行）：

```docker
COPY . /var/www
WORKDIR /var/www
RUN chmod 755 ./start.sh
# 避免PHP无法写入挂载到Volume的文件
CMD ["./start.sh"]
```

测试之后发现我的猜测是正确的√，应用是先挂载了 Volume 之后才开始启动的。

后来我搜索了下发现网上也有一些关于类似的情况的介绍

- [http://blog.csdn.net/wsscy2004/article/details/25980675](http://blog.csdn.net/wsscy2004/article/details/25980675)
- [https://yq.aliyun.com/articles/53990](https://yq.aliyun.com/articles/53990)

至此这个应用基本上已经能用了。应用部署了这个镜像之后每一次启动都会自动把 `/var/www/html` 里面所有文件的所有者和组都改成 `www-data` ，所以如果在 Volume 中新上传文件不能被 PHP 进程操作的话，重启一下应用就好了。

附上最终的 `Dockerfile`

```docker
# 使用官方 PHP-Apache 镜像
FROM daocloud.io/php:5.6-apache
# docker-php-ext-install 为官方 PHP 镜像内置命令，用于安装 PHP 扩展依赖
RUN apt-get update && apt-get install -y php5-curl php5-gd
# pdo_mysql 为 PHP 连接 MySQL 扩展
RUN docker-php-ext-install pdo_mysql
RUN a2enmod rewrite
COPY . /var/www
WORKDIR /var/www
RUN chmod 755 ./start.sh
# 避免php无法写入挂载volume的文件
CMD ["./start.sh"]
```

使用这个 Dockerfile 时记得把 `start.sh` 和 `Dockerfile` 放在同一目录，再用 git 提交到你的仓库中

至此搬迁完成，顺便绑定了域名，由于我的域名没有备案，流量是经过境外服务器反代回国内的服务器的，速度稍慢了些，而且这个反代服务器对 POST 请求的数据大小限制在了10kb以内，使用未备案的自定义域名的话，过长的文章，评论，附件提交将会出现500错误，用分配的二级域名则没有这个限制，有点坑爹。不过网站速度相比我原来的vps还是快了许多，等我有机会再给这个域名备案吧~（ps：现在绑定域名需要备案号了）

--------11月7日更新---------

DaoCloud 计费策略调整，原来的 DaoCloud 云平台更新为“云端测试环境”，每个应用启动24小时后会自动停止，并且会定期清理闲置的应用。对于我这种个人用户来说，网站已经不适合部署在它自有的云平台了，不过利用它构建 Docker 镜像把网站部署到自己的 VPS 上还是非常不错的，所以我又把网站搬回vps
