dokku 部署 Scala 应用实战:从 sbt 构建到 Procfile 进程管理全解析

原创2026-09-09 23:10:401,957 阅读
文章标签:云原生DevOps后端

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 服务能力;postgresql JDBC 驱动用于数据库访问,演示了应用如何通过 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 会依次执行:

  1. builder 检测:通过 builder-herokuish 的 builder-detect 触发,检测项目是否包含 build.sbt 等 Scala 构建特征,从而选择 herokuish Scala 构建包;
  2. 构建:在 herokuish 容器内执行 sbt clean compile stage,产出 target/universal/stage/ 目录;
  3. 进程启动:读取 Procfile 中的 web 进程定义,注入 PORT(容器内默认 5000)等环境变量后启动进程;
  4. 健康检查与代理: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 中复用该样例的注意事项

  1. 路径约定:target/universal/stage/bin/scala-getting-started 的脚本名由 sbt-native-packager 依据 name := "scala-getting-started" 自动生成,若修改项目名,需同步修改 Procfile。
  2. 端口约定:应用必须优先读取 PORT 环境变量,不能硬编码端口,否则代理层无法路由流量。
  3. 依赖管理:sbt 依赖需要能通过 sbt compile 正常解析,构建过程在 herokuish 容器内完成,需保证网络可达 Maven/Ivy 仓库。
  4. JDK 版本:如需不同 Java 版本,通过 system.properties 的 java.runtime.version 声明。
  5. 数据库:/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 即部署"体验。

登录后查看全文
dokku