dokku 部署 Scala 应用实战:从 sbt 构建到 Procfile 进程管理全解析
dokku 部署 Scala 应用实战:从 sbt 构建到 Procfile 进程管理全解析
本篇技术指南以 dokku 仓库中的 Scala 测试样例应用(tests/apps/scala)为主体,完整讲解一个基于 sbt + Finagle 的 Scala 应用如何在 dokku(Docker 驱动的 PaaS)上完成本地运行、容器化构建与一键部署。读完本文,你将掌握 Scala 应用的 Procfile 进程声明、sbt stage 构建产物约定、PORT 环境变量注入机制,以及 dokku 通过 herokuish 构建包自动识别与部署 Scala 应用的核心原理。
一、样例应用概览:scala-getting-started 的结构
dokku 仓库在 tests/apps/scala 目录下内置了一个名为 scala-getting-started 的极简 Scala 应用,用于验证 dokku 对 Scala 语言应用的支持。该应用源自 Heroku 官方入门示例,整体目录结构如下:
tests/apps/scala/
├── Procfile # 进程声明文件,声明 web 进程
├── README.md # 样例应用的说明文档
├── check_deploy # dokku 部署后的健康检查脚本
├── system.properties # JVM 运行时版本声明
├── build.sbt # sbt 构建定义
├── project/
│ ├── build.properties # 锁定 sbt 版本
│ └── plugins.sbt # 引入 sbt-native-packager 插件
└── src/
└── main/
└── scala/
└── com/
└── example/
└── Server.scala # Finagle HTTP 服务入口
各文件职责如下:
| 文件 | 作用 |
|---|---|
build.sbt |
定义项目名称、Scala 版本与依赖库 |
project/build.properties |
锁定 sbt 版本(0.13.5),保证构建可复现 |
project/plugins.sbt |
引入 sbt-native-packager 插件,提供 stage 任务 |
Procfile |
声明 web 进程及其启动命令 |
system.properties |
指定 java.runtime.version=1.7 |
check_deploy |
部署后验证 HTTP 响应内容是否为 "Hello from Scala!" |
二、构建配置:build.sbt 与 sbt 版本锁定
2.1 build.sbt 关键配置
build.sbt 使用 sbt-native-packager 的 packageArchetype.java_application 原型,完整内容如下:
import NativePackagerKeys._
packageArchetype.java_application
name := """scala-getting-started"""
version := "1.0"
scalaVersion := "2.10.4"
libraryDependencies ++= Seq(
"com.twitter" % "finagle-http_2.10" % "6.18.0",
"postgresql" % "postgresql" % "9.0-801.jdbc4"
)
这里的要点:
packageArchetype.java_application:来自 sbt-native-packager 插件(0.7.4 版本),它定义了 Java 应用的打包结构。运行sbt stage后,会在target/universal/stage/目录下生成可直接执行的脚本与依赖 jar。- Scala 版本:固定为 Scala 2.10.4,所有依赖 artifact 使用
_2.10后缀匹配该版本。 - 依赖:
finagle-http提供 HTTP 服务能力;postgresqlJDBC 驱动用于数据库访问,演示了应用如何通过DATABASE_URL环境变量连接 PostgreSQL。
2.2 锁定 sbt 版本
project/build.properties 将 sbt 版本固定为 0.13.5:
sbt.version=0.13.5
该文件确保在任何环境中执行 sbt 时都会使用相同版本,避免因 sbt 版本漂移导致构建行为不一致——这正是云平台部署时保证可复现性的关键实践。
2.3 引入 native-packager 插件
project/plugins.sbt 声明了构建插件:
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.7.4")
sbt-native-packager 是 Scala 应用能在 PaaS 上被识别的核心:它为 sbt 增加了 stage 任务,将项目打包为 target/universal/stage/ 下的标准 Java 应用目录,内含启动脚本和全部依赖。
三、服务实现:Finagle HTTP 服务与 PORT 注入
Server.scala 是应用唯一源码,使用 Twitter Finagle 构建 HTTP 服务:
object Server {
def main(args: Array[String]) {
val port = Properties.envOrElse("PORT", "8080").toInt
println("Starting on port: "+port)
val server = Http.serve(":" + port, new Hello)
Await.ready(server)
}
}
关键设计在于端口获取:
val port = Properties.envOrElse("PORT", "8080").toInt
PORT 环境变量是 PaaS 平台的通用约定。dokku 在部署应用时会自动为容器注入 PORT 环境变量(容器内默认为 5000),应用必须监听该端口才能被代理层(如 nginx)转发流量。这里的 envOrElse 保证了本地开发(无 PORT 环境变量时回退到 8080)与云端部署(读取注入的 PORT)两种场景都能正常工作。
服务还实现了两个路由:
/根路径:返回固定字符串Hello from Scala!;/db路径:通过DATABASE_URL解析 PostgreSQL 连接信息(用户、密码、主机、路径),执行建表与插入时间戳操作并回显数据库内容。
getConnection() 展示了如何从 DATABASE_URL 解析连接参数:
def getConnection(): Connection = {
val dbUri = new URI(System.getenv("DATABASE_URL"))
val username = dbUri.getUserInfo.split(":")(0)
val password = dbUri.getUserInfo.split(":")(1)
val dbUrl = "jdbc:postgresql://" + dbUri.getHost + dbUri.getPath
DriverManager.getConnection(dbUrl, username, password)
}
这种从 DATABASE_URL 环境变量解析连接串的方式,是 Heroku 及 dokku 生态中数据库服务注入的标准做法。
四、进程声明:Procfile 与 sbt stage 产物
Procfile 仅有一行,却定义了部署后的进程形态:
web: target/universal/stage/bin/scala-getting-started
理解这行的关键在于构建产物路径。执行 sbt stage 后,sbt-native-packager 会在 target/universal/stage/ 下生成:
bin/scala-getting-started:可直接执行的启动脚本(会自动设置 classpath);lib/:收集的全部依赖 jar 与项目产物。
Procfile 中的 web 是唯一进程类型。dokku 的 builder-herokuish 插件通过 herokuish 构建包执行 sbt clean compile stage(对应 Scala 构建包),随后读取 Procfile,将 web 进程作为默认 Web 进程启动。进程启动时,dokku 会注入 PORT 环境变量,因此该脚本启动的 JVM 进程最终监听容器内的 PORT 端口。
五、JVM 版本声明:system.properties
system.properties 用于向构建包声明运行时要求:
java.runtime.version=1.7
在 Heroku 生态中,system.properties 是 JVM 类应用声明 Java 运行时版本的约定文件,Scala 构建包会读取 java.runtime.version 选择对应的 JDK。对 dokku 而言,部署时由 herokuish 内部的 Scala/Java 构建包处理该文件。
六、部署验证:check_deploy 脚本
check_deploy 是 dokku 测试框架用来验证部署是否成功的健康检查脚本:
#!/usr/bin/env bash
set -e
output="$(curl -s -S "$1")"
echo "$output"
test "$output" == "Hello from Scala!"
其逻辑非常直观:对传入的 URL 发起 curl 请求,断言响应内容精确等于 Hello from Scala!。这直接对应 Server.scala 中 showHome 返回的字符串,从而端到端验证了「构建 → 部署 → 端口转发 → HTTP 响应」整条链路。
七、本地运行方式(继承自原文档)
原 README 给出的本地运行步骤如下(需预先安装 Scala、sbt 及 Foreman):
git clone <仓库>
cd <应用目录>
sbt compile stage
foreman start web
sbt compile stage:编译源码并生成可发布产物;foreman start web:按 Procfile 声明启动web进程。
本地启动后,应用监听 5000 端口(Foreman 默认端口),浏览器访问 http://localhost:5000/ 即可看到 Hello from Scala! 响应。
八、部署到 dokku:从本地推到云端
在 dokku 上部署该应用与 Heroku 的 git push heroku master 流程同构,完整步骤如下:
# 1. 在 dokku 主机上创建应用
dokku apps:create scala-getting-started
# 2. 为应用绑定 PostgreSQL 数据库(若需 /db 功能)
dokku postgres:create scala-db
dokku postgres:link scala-db scala-getting-started
# 3. 本地添加 dokku 远程仓库并推送
git remote add dokku dokku@<dokku-host>:scala-getting-started
git push dokku master
# 4. 打开应用
dokku open scala-getting-started
部署过程中 dokku 会依次执行:
- builder 检测:通过 builder-herokuish 的
builder-detect触发,检测项目是否包含build.sbt等 Scala 构建特征,从而选择 herokuish Scala 构建包; - 构建:在 herokuish 容器内执行
sbt clean compile stage,产出target/universal/stage/目录; - 进程启动:读取 Procfile 中的
web进程定义,注入PORT(容器内默认 5000)等环境变量后启动进程; - 健康检查与代理:dokku 根据应用的
check_deploy语义对 Web 进程做可达性验证,并将http://<app>.<dokku-domain>路由到容器端口。
dokku 的端到端验证逻辑可以参考 tests/unit/ps-herokuish-1.bats:该测试通过 deploy_app 部署 herokuish 类型应用,随后依次执行 dokku ps:stop、dokku ps:start、dokku ps:restart、dokku ps:rebuild,并检查容器状态与进程管理行为,印证了 dokku 对构建包类应用的完整进程生命周期管理能力。
九、在 dokku 中复用该样例的注意事项
- 路径约定:
target/universal/stage/bin/scala-getting-started的脚本名由 sbt-native-packager 依据name := "scala-getting-started"自动生成,若修改项目名,需同步修改 Procfile。 - 端口约定:应用必须优先读取
PORT环境变量,不能硬编码端口,否则代理层无法路由流量。 - 依赖管理:sbt 依赖需要能通过
sbt compile正常解析,构建过程在 herokuish 容器内完成,需保证网络可达 Maven/Ivy 仓库。 - JDK 版本:如需不同 Java 版本,通过
system.properties的java.runtime.version声明。 - 数据库:
/db路由依赖DATABASE_URL环境变量,未配置时访问会抛异常,属于可选的演示功能。
十、总结
tests/apps/scala 虽然是一个入门级样例,却完整覆盖了在 dokku 上部署 Scala 应用的全部关键点:build.sbt + sbt-native-packager 的构建产物约定、Procfile 的进程声明、PORT 环境变量的运行时注入、system.properties 的 JVM 版本控制,以及 DATABASE_URL 驱动的数据库集成。理解这一套约定,就能把任意基于 sbt 的 Scala 应用平滑迁移到 dokku 上,享受与 Heroku 一致的"git push 即部署"体验。