Ghost docker安装踩坑

0 阅读8分钟

ghost 功能介绍

Ghost 是一款基于 Node.js 构建的开源内容管理系统,主打简洁高效的博客和出版平台。它支持 Markdown 编辑,内置会员及订阅功能,丰富站点主题供选择,适合开发者和内容创作者搭建个性化网站。截至 2024 年,Ghost 已拥有数百万用户和活跃社区,持续保持稳定迭代。官方 GitHub 项目地址为:github.com/TryGhost/Gh…

安装方式

官网给出四种安装方式:

  1. Ghost CLI - 适合在服务器上直接部署,不需要对ghost做任何改动。由于很难横向扩展,不适合线上环境部署。
  2. Docker 安装 - 通过ghost官方镜像安装,适合大部分环境部署。
  3. 本地安装 - 在本地安装ghost,可以通过docker镜像build的方式得到适合线上部署的镜像。
  4. 源码安装 - 从Git安装ghost,适合Ghost贡献者或者需要大范围修改内部代码。

因为线上使用,我选择的是 Docker 安装的方式,以下也是这个方式踩过的坑。

ghost部署架构图

未命名绘图.drawio (1).png

底层基础设施依托AWS云服务,涵盖部署环境、邮件服务及系统监控。Ghost 通过适配器模式集成上述能力,实现云服务的标准化接入。同时,Ghost 提供包括支付处理、性能监测及运营分析在内的功能模块,支撑站点稳定运行与数据观测。

踩坑问题

镜像问题

  • Q: [镜像剪枝] ghost docker安装方式默认使用docker compose运行,并集成了cabby等服务。多个镜像在同一台机器上,一方面不是所有服务都必须运行,另一方面不同服务之间可能会竞争资源,影响性能。

    A: 只启动ghost镜像,其他服务不运行。需要流量监控时,可以将相关服务单独部署,并通过服务间数据上报实现监控。

  • Q: [分级部署] 官方方案中,使用docker compose会加载对应环境的env文件,自动进行分级部署。但ghost单镜像的分级部署方式没有详细说明。

    A: 根据ghost源码,ghost运行时会自动加载/var/lib/ghost目录下相应环境的config.[env].json配置文件。

  • Q: [镜像架构不匹配] ghost启动后失败,日志打印 exec /usr/local/bin/docker-entrypoint.sh: exec format error

    A: 出现 exec format error 的原因是镜像的系统架构与服务器不匹配。比如:在 Mac(通常为 arm64 架构)上拉取镜像并推送到镜像库,但在 Linux 服务器(通常为 x86_64 架构)上运行时,二进制文件架构不兼容,导致无法启动。

  • Q:【分级配置不生效】基于官方镜像制作新的镜像的dockerfile中,将config.[env].json复制到配置的文件夹下后,通过 NODE_ENV 设置工作模式,镜像中会加载对应环境配置。但是在部署线上环境时,发现仍然使用测试环境的配置。对应dockerfile部分如下:

image.png

A: 检查镜像发现,复制目标位置存在一份配置文件:config.production.jsonconfig.development.json,并且前者为后者的软链。复制操作会对同一文件先用config.production.json覆盖,再用config.development.json覆盖,导致最后只有dev环境的配置。修改镜像解除二者的软链关系,再打出对应镜像即可。

配置问题

  • Q: 部署实例后,访问没有返回

    A: 这个问题可能的原因有很多。以下是我实际遇到的一种情况:

    网站需要运行在 https://medo.dev/blog 路径下,因此在 config.json 中将 url 配置为 https://medo.dev/blog,这样所有请求都会带有 /blog 前缀。

    但是,反向代理(如 Nginx)在将流量转发到实例时,必须携带 /blog 路径,否则 Node.js 服务只会处理根路径 / 下的请求,导致访问没有返回或资源加载失败。

  • Q: 接口服务响应时浏览器报错 mix content

    A: 出现“mixed content”错误,通常是因为前端页面以 HTTPS 协议访问,而接口服务(API)却用 HTTP 协议响应,违反了浏览器的安全策略。

    在 Ghost 这类应用中,config.json 配置里的 url 字段不仅影响页面资源请求地址前缀,同时还影响 Ghost 服务监听和响应时的协议处理:

    • 如果 url 配置为 HTTPS,且使用 ALB(如 AWS 的 Application Load Balancer)做 HTTPS 协议卸载,ALB 转发到 Ghost 实例时用 HTTP 协议,这时 Node 服务会收到 HTTP 请求,但检测到 url 配置是 HTTPS,于是会重定向到 HTTPS,造成页面不断 301 跳转。
    • 如果 url 配置为 HTTP,则不会重定向,但页面用 HTTPS 加载,接口/资源为 HTTP,便触发 mixed content 报错。

    官方推荐的解决办法: 在 ALB 层增加 header 配置,把 X-Forwarded-Proto: https 透传到 Ghost 服务器。Ghost 会根据该 header 判断实际访问协议,从而正确处理请求和响应,避免重定向与 mixed content 问题。

    解决办法: 通过自定义镜像,修改 Ghost 源码或 Express 服务,去掉 301 协议校验

image.png

  • Q:部署完成,在管理后台通过邀请链接邀请其他人使用,用户点击链接登录、邮箱验证之后,又跳转回登陆页面。

    A: 当前部署架构是在负载均衡后的某一个path指向部署服务器。用户登录后,服务端设置用户登陆信息的cookie后,对应的 set-cookie header 没有带到浏览器中。对应的解决方式是两种:

  • Nginx 配置 proxy_cookie_path 强制设置 SameSite=None; Secure,允许服务器返回的header透传给浏览器 1. 修改源码镜像中中携带的cookie强制为sameSite,以及secure、domain信息如下,并且在 config.[env].json 中增加配置 root.session.cookie.path 为服务器 serve path,root.session.cookie.domain 为服务域名

image.png

适配器问题

  • Q: S3 适配器安装后项目不能启动

    A: 从日志(docker log)看到类似如下报错:

ERROR Unable to find storage adapter s3 in ,/var/lib/ghost/content/adapters/,/var/lib/ghost/versions/6.10.3/core/server/adapters/.

这说明 Ghost 在配置文件里尝试加载 S3 存储适配器,但在上述路径下没有找到正确的适配器文件。

排查时发现:

  • 通过 docker exec 进入容器,适配器文件其实已经放到对应路径。
  • 但 Ghost 并不识别该适配器,依然报错。

原因分析:

Ghost 要求适配器为打包好的index.js文件,且该文件应作为模块入口放在适配器路径下。例如:

/var/lib/ghost/content/adapters/storage/s3/index.js

如果只是将源码文件复制进去,Ghost 可能无法正确加载。需要将源码打包成一个标准的 Node.js 模块入口文件。

解决方法:

  1. 使用esbuild工具将 S3 适配器打包为单个index.js文件:
  2. 将生成的index.js放到 Ghost 指定的存储适配器目录下。例如:/var/lib/ghost/content/adapters/storage/s3/index.js
  • Q: redis连接超时,日志表现首请求处理时间长,页面持续loading

    A: 这是由于 Ghost 官方文档中 Redis 的配置不完整或缺失,导致 Ghost 在首次请求时尝试连接 Redis 超时,表现为首请求处理时间很长,页面一直 loading。

{
  "adapters": {
    "cache": {
      "Redis": {
        "host": "master.redis-host.com",
        "port": 6379,
        "password": "password",
        "storeConfig": {
          "tls": {} // 使用 tls 加密协议
        }
      },
      "imageSizes": {
        "adapter": "Redis",
        "host": "master.redis-host.com",
        "port": 6379,
        "password": "password",
        "ttl": 60,
        "tls": {
          "rejectUnauthorized": false
        },
        "keyPrefix": "image-sizes:"
      }
    }
  }
}

AWS 配置问题

  • Q: S3 上传报错 The bucket does not allow ACLs

A: 这是因为 S3 的存储桶(Bucket)开启了“Object Ownership(对象所有权)”的新策略,并设置为了“Bucket owner enforced(强制桶所有者拥有权限)”。此配置会自动禁用所有 ACL(访问控制列表),也就是不能在上传对象时设置ACL参数,否则就会报错The bucket does not allow ACLs

解决办法:

  1. 修改 Bucket 的 Object Ownership 设置:

    • 进入 S3 控制台,找到Bucket。
    • 在“权限” > “对象所有权”里,把属性由“Bucket owner enforced”改为“ACLs enabled”,允许使用 ACL。

原因说明:

  • AWS S3 2023年后新建 Bucket 默认开启“Bucket owner enforced”,自动禁止所有 ACL,推荐用 Bucket Policy 管理权限。
  • 只要上传时有 ACL 字段(甚至是ACL: null)都会报错。
  • Q: S3上传报错 is not authorized to perform: s3:PutObjectAcl

    A: 这是因为 S3 上传对象时,如果指定了 ACL(如ACL: 'public-read'),需要同时拥有s3:PutObjectAcl权限。而你的 IAM 用户的策略只授予了s3:PutObject,没有包含s3:PutObjectAcl,导致报错。

    解决办法:为 IAM 用户增加s3:PutObjectAcl权限在 S3 的 policy(策略)中,添加如下权限:s3:PutObjectAcl这样就允许该用户在上传对象时设置 ACL。

  • Q: 项目启动时报错: Migration lock was never released or currently a migration is running

    A: 这是因为项目在初始化数据库迁移时被中断(如进程异常终止或网络原因),导致迁移锁(migration lock)没有被释放。这个锁通常是由数据库中的某条记录控制,防止多进程同时迁移。

    在 AWS 云数据库环境下,如果是首次初始化且数据库还没有数据,可以直接删除整个数据库,然后重新执行初始化流程即可,锁定问题会自动解决。

总结

Ghost本身能力非常完整,如果独立域名自建部署的方式,由于网络路径短,遇到的问题和排查难度都会降低不少;但是在既有域名的subpath下指向ghost服务,会遇到一些怪异的问题。这种情况下,可以通过AI IDE读源码的方式,十分快速的解决遇到的问题。