Dokku Deployment Tasks 指南:使用 app.json 与 Procfile release 在部署生命周期中执行任务
【免费下载链接】dokkuA docker-powered PaaS that helps you build and manage the lifecycle of applications项目地址: https://gitcode.com/GitHub_Trending/do/dokku
Dokku 的 Deployment Tasks 机制允许你在应用完全部署之前或之后的关键节点执行命令,典型场景包括数据库初始化检查、数据库迁移、静态资源收集(如 Django 的collectstatic)、CDN 同步与缓存预热。本文以官方文档 docs/advanced-usage/deployment-tasks.md 为主线,结合 app-json 插件 源码与 单元测试,完整讲解app.json的scripts.dokku.predeploy、scripts.dokku.postdeploy、scripts.postdeploy与Procfile的release命令的使用时机、执行顺序、镜像提交语义,以及appjson-path属性的配置与排查方法。
什么是 Deployment Tasks
在 Dokku 中,应用的部署由 "release"(镜像构建完成后的发布阶段)与 "deploy"(调度容器)两大部分组成。Deployment Tasks 是在这一流程中插入的"钩子",用于在应用尚未完全就绪时执行一次性或每次部署都会运行的操作。
官方文档给出的典型用例包括:
- 检查数据库是否已初始化;
- 运行数据库迁移;
- 执行任何需要为服务器准备环境的命令(例如 Django 的
collectstatic)。
所有任务都在构建好的 Docker 镜像上下文中执行——除非应用挂载了 volume,否则这些命令不会影响宿主机。
需要在 release 阶段于宿主机上执行命令的场景,请参考 插件创建文档 构建自定义插件。
四种任务阶段的选择与限制
Dokku 提供四类 Deployment Task,各自有不同的适用场景与镜像变更提交语义:
| 任务阶段 | 来源 | 执行时机 | 镜像变更是否提交 | 典型用例 |
|---|---|---|---|---|
scripts.dokku.predeploy | app.json | 镜像构建完成之后、容器调度之前 | 是 | 以不同方式打包资源;从源码安装自定义包或将二进制文件复制到镜像中 |
scripts.dokku.postdeploy | app.json | 容器调度之后 | 否 | 通知 Slack 部署完成;与中心负载均衡器协调流量路由 |
scripts.postdeploy | app.json | 容器调度之后,且仅应用首次创建部署时执行一次 | 否 | 设置 OAuth 客户端与 DNS;向应用的测试数据库加载种子/测试数据 |
release | Procfile | 镜像构建完成之后、容器调度之前,且在scripts.dokku.predeploy之后 | 否 | 将 CSS、JS 等资源从 slug 发送到 CDN/S3;预热或失效缓存;运行数据库迁移 |
选择建议:
- 需要修改镜像内容(如安装包、复制二进制)时,使用
scripts.dokku.predeploy,因为只有该阶段会把变更提交回镜像; - 只通知外部系统、不改变镜像时,使用
scripts.dokku.postdeploy或Procfile的release; - 只在应用首次创建后执行一次(如初始化 OAuth、加载种子数据)时,使用
scripts.postdeploy。
各阶段失败时的行为
WARNING:任何失败的
app.jsonDeployment Task 都会导致部署失败。但无论哪个阶段失败,都不会影响已运行的容器(deploy 会回滚到上一版本)。
与 Dockerfile ENTRYPOINT 的交互
如果应用使用 Dockerfile 构建且声明了ENTRYPOINT,Deployment Task 的命令会原样传递给该 entrypoint。唯一的例外是以下四种 tini 包装型 entrypoint,它们会被跳过(即任务命令直接以 shell 执行,不再经过 entrypoint):
["/tini", "--"]["/bin/tini", "--"]["/usr/bin/tini", "--"]["/usr/local/bin/tini", "--"]
这一逻辑在 plugins/app-json/functions.go 的constructScript中有源码级实现:nonSkippableEntrypoints映射中列出的 tini entrypoint 会被"跳过",其余 entrypoint 存在时,命令会通过shellquote.Split拆分为单词后原样作为容器命令执行。
配置 app.json 的位置(appjson-path 属性)
Dokku 会按部署方式在不同的基准目录中查找app.json:
- 通过
git:from-image与git:load-image部署时:基准目录为 Docker 镜像的WORKDIR; - 其他部署方式(
git push、git:from-archive、git:sync):基准目录为源码树根目录。
在 monorepo 等场景下,你可能希望指定其他路径,可通过app-json:set命令设置appjson-path属性:
dokku app-json:set node-js-app appjson-path .dokku/app.json关键语义:
- 该值始终是相对于基准搜索目录的路径,在任何上下文中都不会被当作绝对路径处理;
- 若该文件在仓库中不存在,Dokku 会继续构建流程,如同仓库没有
app.json一样(参见 tests/unit/app-json.bats 中的app-nonexistent.json测试); - 传入空值可恢复默认:
dokku app-json:set node-js-app appjson-pathappjson-path也支持全局设置,全局默认值为app.json,仅当应用未设置专属值时才会使用全局值:
dokku app-json:set --global appjson-path global-app.json同样,传入空值即可恢复默认:
dokku app-json:set --global appjson-path从源码看,appjson-path是 app-json 插件唯一支持的属性:在 plugins/app-json/appjson.go 中,DefaultProperties与GlobalProperties均只声明了appjson-path一个键。
查看 app-json 报告(app-json:report)
IMPORTANT:
app-json:report自 0.25.0 起可用。
运行app-json:report(不带应用名则列出所有应用):
dokku app-json:report输出示例:
=====> node-js-app app-json information App-json computed appjson path: app2.json App-json global appjson path: App-json appjson path: app2.json =====> python-sample app-json information App-json computed appjson path: app.json App-json global appjson path: App-json appjson path: =====> ruby-sample app-json information App-json computed appjson path: app.json App-json global appjson path: App-json appjson path:三个字段的含义:
appjson-path:应用级原始值,未设置时为空;global-appjson-path:全局原始值,未设置时为空;computed-appjson-path:部署时实际生效的值,优先级为:应用级值 → 全局值 → 内置默认值app.json。
指定应用查询:
dokku app-json:report node-js-app只输出单项值(便于脚本消费),使用--app-json-appjson-path等 flag:
dokku app-json:report node-js-app --app-json-appjson-path输出:
app2.jsoncomputed-appjson-path的计算逻辑在 plugins/app-json/report.go 中实现:依次取应用级值、全局值,均为空时回落到"app.json"。支持的其他 flag 还包括--app-json-computed-appjson-path与--app-json-global-appjson-path。该行为有完整的单元测试覆盖,见 tests/unit/app-json.bats(round-trip、raw vs computed vs global 等用例)。
编写 Deployment Tasks
app.json 部署任务
Dokku 对 Heroku 的app.json清单提供有限支持,与 Deployment Tasks 相关的键有三个:
scripts.dokku.predeploy:在应用 Docker 镜像构建之后、任何容器调度之前运行;此阶段的镜像变更会被提交;scripts.dokku.postdeploy:在应用容器调度之后运行;此阶段的镜像变更不会被提交;scripts.postdeploy:在应用容器调度之后运行;此阶段的镜像变更不会被提交,且仅在应用首次创建部署时执行一次。
目前 Dokku 仅支持上述scripts.dokku.predeploy与scripts.dokku.postdeploy(scripts.postdeploy同样可用),app.json中的其他字段会被忽略,可以省略。示例:
{ "scripts": { "dokku": { "predeploy": "touch /app/predeploy.test", "postdeploy": "curl https://some.external.api.service.com/deployment?state=success" }, "postdeploy": "curl https://some.external.api.service.com/created?state=success" } }从源码看,这三个键在 plugins/app-json/appjson.go 的Scripts结构体中均有对应的 JSON 字段映射(scripts.dokku.predeploy、scripts.dokku.postdeploy、scripts.postdeploy),而解析函数ReadAppJSON使用 hujson 解析,意味着文件内容允许 JSONC(带注释的 JSON)语法。取值逻辑在getPhaseScript(plugins/app-json/functions.go)中:heroku.postdeploy阶段读取Scripts.Postdeploy,predeploy阶段读取Scripts.Dokku.Predeploy,其余阶段读取Scripts.Dokku.Postdeploy。
Procfile release 命令
IMPORTANT:
release命令自 0.14.0 起可用。
Procfile支持特殊的release命令,行为类似 Heroku 的 Release Phase:在应用 Docker 镜像构建之后、任何容器调度之前执行,且运行于scripts.dokku.predeploy之后。使用方法是在 Procfile 中添加release段:
release: curl https://some.external.api.service.com/deployment?state=built与scripts.dokku.predeploy不同,release阶段对磁盘的变更不会持久化到镜像。
WARNING:对
release命令进行扩容(scaling)很可能会引发部署中的未知问题,强烈不建议这样做。
release命令的读取通过procfile-get-command触发器完成(plugins/app-json/functions.go),并在executeScript中以phaseSource = "Procfile"标记来源。
底层执行机制:任务是如何运行的
从源码层面看,Deployment Tasks 的执行由 app-json 插件的两个核心触发器驱动:
pre-release-builder(plugins/app-json/triggers.go):在处理完app.json中的环境变量之后执行predeploy任务;post-release-builder(plugins/app-json/triggers.go):依次执行release任务、依据app.json的formation设置进程伸缩,并在首次部署时(通过heroku.postdeploy属性做幂等标记)执行scripts.postdeploy;post-deploy(plugins/app-json/triggers.go):执行scripts.dokku.postdeploy,且会提前判断任务是否存在,避免对无 postdeploy 任务的应用做多余的镜像解析。
这些触发器由部署主流程调用:release_and_deploy(plugins/common/functions)先调用dokku_release触发builder-release,成功后通过cmd-deploy触发scheduler-deploy,从而形成"构建 → predeploy → release → 调度容器 → postdeploy"的完整链条。
任务的容器执行细节体现在executeScript(plugins/app-json/functions.go)中:
- 通过
docker-args-deploy与docker-args-process-deploy触发器收集部署参数,并过滤掉--cpus、--memory、--publish、--restart、-p、-P等资源限制与端口发布类参数; - 为任务容器打上
dokku_phase_script=<phase>标签,herokuish 镜像额外挂载cache-<app>:/tmp/cache,CNB(pack)镜像则使用--entrypoint=/cnb/lifecycle/launcher覆盖默认入口; - 以应用的 shell(
DOKKU_APP_SHELL)执行set -e包装的脚本,herokuish 镜像还会先 source/app/.profile.d/*并设置HOME=/app;以/开头的命令会先校验二进制可执行性(plugins/app-json/functions.go); - 任务失败时输出容器日志并令整个部署失败;
- 只有
predeploy阶段会把执行容器的变更通过docker container commit提交回镜像(同时保留 Dockerfile 的ENTRYPOINT/CMD变更,并写入com.dokku.app-name、com.dokku.<phase>-phase标签),其他阶段执行完毕后容器被清理、变更丢弃。
这一"提交与否"的区别在 tests/unit/app-json.bats 中得到验证:predeploy写入的/app/predeploy.test在后续dokku run中仍存在,而postdeploy写入的/app/postdeploy.test不存在。
完整示例:一次带迁移与通知的部署
综合以上内容,一个完整的实践配置如下。
项目根目录app.json:
{ "scripts": { "dokku": { "predeploy": "python manage.py collectstatic --noinput", "postdeploy": "curl -X POST https://hooks.example.com/deploy?state=success" }, "postdeploy": "curl -X POST https://hooks.example.com/app-created" } }项目根目录Procfile:
web: gunicorn myapp.wsgi --bind 0.0.0.0:$PORT release: python manage.py migratemonorepo 场景下指定清单文件位置:
dokku app-json:set my-app appjson-path services/web/app.json随后正常推送代码即可触发上述全部任务,部署日志中会出现类似下面的提示(对应 tests/unit/app-json.bats 中的断言文本):
Executing predeploy task from app.json: python manage.py collectstatic --noinput Executing release task from Procfile in ephemeral container: python manage.py migrate Executing postdeploy task from app.json in ephemeral container: ...如需排查某个应用实际使用的 app.json 路径,直接查询计算值:
dokku app-json:report my-app --app-json-computed-appjson-path小结
Deployment Tasks 将"部署前的镜像准备"与"部署后的外部协调"以声明式清单(app.json)和进程文件(Procfile)的形式固化下来。使用时把握三点即可:需要变更镜像内容就用scripts.dokku.predeploy;仅通知外部系统就用scripts.dokku.postdeploy或Procfile release;只执行一次的应用初始化用scripts.postdeploy。配合appjson-path与app-json:report,monorepo 与多应用环境下也能精确控制任务清单的位置与生效路径。
【免费下载链接】dokkuA docker-powered PaaS that helps you build and manage the lifecycle of applications项目地址: https://gitcode.com/GitHub_Trending/do/dokku
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考