通过 Kamal 部署 Web 服务 的一段时间后,还是决定放弃 Kamal,理由如下:
- 官方文档过于简洁,很多地方只是一句带过,似乎它预设用户是一个部署经验丰富的工程师。导致遇到问题就得去网上自己找翻找答案、甚至翻源码。
- 在 Phoenix 项目中使用时,环境变量的处理流程过于复杂(4 步才能流到代码里,多出来了中间两个环节):系统环境变量 –>
.kamal/secrets
–>configs/deploy.yaml
–>configs/runtime.exs
一番研究和对比后,决定转向 Dokku,Heroku 的开源竞品。生态远比 Kamal 丰富,文档也很齐全。
Dokku 工作原理
首先得解释下 Dokku 大概的工作原理,这对于搞清楚状况、排查问题很有帮助,如果已经清楚,可以直接跳到下面看「部署步骤」:
- 它是一个安装在服务器上的程序,运行了一个 Git 服务器(当然还有别的模块)。当你在其他地方的代码仓库里向该 Git 服务器推送代码时,它会收到通知(Git Hook)然后运行部署流程。
- 你的程序在服务器上是通过 Docker 实例的方式运行的。而 Docker Image 是在你推送代码到服务器上后,Dokku 在服务器上构建出来的。构建时优先使用 Dockefile,没有的话就用 Herokuish(约等于 Heroku 的 Buildpack,即用来打包 Docker Image 的一系列脚本)
部署步骤
首先,最终部署的成果: Phoenix 程序,连接 Postgres 数据库,使用 Cloudflare 托管的域名。
下面是看官方文档 + 网友文章 + 踩坑后整理出来的步骤:
- 参考官方文档 在服务器上安装好 Dokku、设置好 SSH 密钥和域名
- 在服务器上,创建一个应用程序(和你本地开发的程序同名即可)
# on the Dokku host
dokku apps:create ruby-getting-started
- 安装 dokku postgres 插件(railsdatabase 名字可以修改)
# on the Dokku host
# install the postgres plugin
# plugin installation requires root, hence the user change
sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git0
# create a postgres service with the name railsdatabase
dokku postgres:create railsdatabase
- 给应用追加数据库访问 URL 的配置
postgres:link 的原理就是
# on the Dokku host
# each official datastore offers a `link` method to link a service to any application
dokku postgres:link railsdatabase ruby-getting-started
- 追加 Herokuish(buildpacks)
echo "https://github.com/HashNuke/heroku-buildpack-elixir.git" >> .buildpacks
# 注意,使用 gigalixir 的,它解决了 https://github.com/gjaldon/heroku-buildpack-phoenix-static.git 仓库里的一个问题:https://github.com/gjaldon/heroku-buildpack-phoenix-static/issues/127
echo "https://github.com/gigalixir/gigalixir-buildpack-phoenix-static.git" >> .buildpacks
- 配置 buildpacks
# 根目录下创建 elixir_buildpack.config
touch elixir_buildpack.config
将下面内容添加进去。
注意根据你自己的项目的实际需要调整环境变量名(config_vars_to_export),下面是我自己的版本
# 版本名字必须包含小数点,也就是跟链接里的出现的一模一样才行,我之前设置的27,也不行
erlang_version=27.0
# Elixir version
elixir_version=1.18.1
# Always rebuild from scratch on every deploy?
always_rebuild=true
# Export heroku config vars
config_vars_to_export=(DATABASE_URL GMAIL_PASSWORD GMAIL_USERNAME SECRET_KEY_BASE)
# 根目录下创建 phoenix_static_buildpack.config
touch phoenix_static_buildpack.config
将下面内容添加进去
# 声明一个比较高的node版本,否则会安装一个非常低的node版本,并且最后报错
node_version=23.11.0
npm_version=10.9.2
- 降级 Dokku 上的 Heroku Stack 配置 前面安装的 HashNuke/heroku-buildpack-elixir 不支持最新的 stack heroku-24。但在服务器上安装的 Dokku 已经默认使用的是最新的 stack heroku-24, 所以会导致在部署时报错
=====> Downloading Buildpack: https://github.com/HashNuke/heroku-buildpack-elixir.git
=====> Detected Framework: Elixir
-----> Will export the following config vars:
CURL_CONNECT_TIMEOUT
CURL_TIMEOUT
DATABASE_URL
DOKKU_APP_TYPE
GIT_REV
GMAIL_PASSWORD
GMAIL_USERNAME
dokuya_GEMINI_API_KEY
SECRET_KEY_BASE
STRIPE_LITE_MONTHLY_PRICE_ID
STRIPE_PLUS_MONTHLY_PRICE_ID
STRIPE_SECRET
STRIPE_ULTIMATE_MONTHLY_PRICE_ID
STRIPE_WEBHOOK_SECRET
* MIX_ENV=prod
-----> Checking Erlang and Elixir versions
Will use the following versions:
* Stack heroku-24
* Erlang 27.2
* Elixir v1.18.1
Sorry, Erlang '27.2' isn't supported yet or isn't formatted correctly. For a list of supported versions, please see https://github.com/HashNuke/heroku-buildpack-elixir#version-support
remote: ! Failure during app build
remote: ! Removing invalid image tag dokku/dokuya:latest
remote: ! App build failed
To dokuya.ai:dokuya
! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to 'dokuya.ai:dokuya'
要解决这个问题的话,就需要降级 Dokku 支持的 Heroku Stack 为 heroku-22,因为上面这个库支持它,而且里面也有最新版本的 erlang
# 检查应用当前使用的 stack
# 记得修改 my-app 成你的应用名称
dokku buildpacks:report my-app
# 切换到 heroku-22
dokku buildpacks:set-property my-app stack gliderlabs/herokuish:latest-22
- 给程序提前设置好环境变量
在服务器上使用
# 可以同时设置多个环境变量
# 每次设置后,都会触发重启应用程序。如果程序还没部署也没关系,尽管设置
dokku config:set my-app ENV=prod COMPILE_ASSETS=1
- 自动更新数据库迁移文件
我的项目需要操作数据库,本地的 Phoenix 程序中存在一些迁移文件,我希望在每次部署时都能自动执行迁移命令。
这需要在项目根目录下创建一个 app.json
文件
{
"scripts": {
"dokku": {
"postdeploy": "mix ecto.migrate"
}
}
}
Dokku 还有其他部署钩子可以用在其他用途,可以参考Dokku 文档。
- 设置新的远程 git origin
git remote add dokku dokku@<mydomain.com>:<app_name>
将你的 git 公钥添加到 Dokku 服务器上
公钥地址(假如你之前本地已经配置过 ssh 登录 Github 的话,一般是存在这个文件):~/.ssh/id_rsa.pub
将文件内容手动追加到服务器上的 ~/.ssh/authorized_keys
文件中
- 部署程序
以后只要想部署一次代码,就在本地项目下运行该命令即可:
git push dokku main
但是可能你的仓库之前是保存在 Github 里,所以每次每次部署完,还是需要手动再推送代码到 Github :
git push
其他:
- 因为我的域名托管在 Cloudflare,所以可以直接利用它的Proxy给域名提供 https 服务。就不需要使用 Dokku 的 https 相关功能了。