Skip to content

Commit ca198ce

Browse files
authored
use sbt-native-packager to build image, move from java 17 to 25 (#344)
* use sbt-native-packager to build image, move from java 17 to 21 * prevent setting latest tag when not pure semver * move to java 25 since sbt, scala and play support java 25 after upgrading dependencies, we can give it a shot. smoke tested locally. * keep v prefix for backend docker tag * log directory/permissions * only log to stdout
1 parent 497ffbb commit ca198ce

File tree

13 files changed

+875
-508
lines changed

13 files changed

+875
-508
lines changed

.github/workflows/build_backend.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
2626
with:
2727
distribution: temurin
28-
java-version: 17
28+
java-version: 25
2929
- name: Build backend
3030
working-directory: ./backend
3131
run: sbt clean update compile test dist

.github/workflows/publish.yml

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,17 @@ jobs:
4242
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
4343
with:
4444
distribution: temurin
45-
java-version: 17
46-
- name: Build backend
47-
working-directory: ./backend
48-
run: sbt clean update compile test dist
45+
java-version: 25
4946
- name: Add COMMIT_SHORT_SHA env property
5047
run: echo "COMMIT_SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
51-
- name: Set up Docker Buildx
52-
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
5348
- name: Login to DockerHub
5449
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
5550
with:
5651
username: ${{ secrets.DOCKERHUB_USERNAME }}
5752
password: ${{ secrets.DOCKERHUB_TOKEN }}
58-
- name: Build & push backend image to docker hub
59-
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11
60-
with:
61-
context: ./backend
62-
push: true
63-
tags: tegonal/lasius-backend:${{github.ref_name}}
53+
- name: Build & push backend image to docker hub via sbt-native-packager
54+
working-directory: ./backend
55+
run: |
56+
# sbt-native-packager will compile, test, and build Docker image
57+
# The version is automatically set by git tag via dynver plugin
58+
sbt clean update compile test docker:publish

backend/.dockerignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Build artifacts
2+
target/
3+
project/target/
4+
project/project/
5+
6+
# IDE files
7+
.idea/
8+
.vscode/
9+
*.iml
10+
.bsp/
11+
12+
# Version control
13+
.git/
14+
.gitignore
15+
16+
# Logs
17+
logs/
18+
*.log
19+
20+
# Temp files
21+
*.tmp
22+
*.bak
23+
*.swp
24+
*~
25+
26+
# OS files
27+
.DS_Store
28+
Thumbs.db
29+
30+
# Test data
31+
test-data/
32+
33+
# Node modules (if any)
34+
node_modules/
35+
36+
# Documentation
37+
*.md
38+
docs/

backend/.java-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
21
1+
25

backend/Dockerfile

Lines changed: 0 additions & 7 deletions
This file was deleted.

backend/app/controllers/MessagingController.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class MessagingController @Inject() (
6868
authConfig = authConfig
6969
),
7070
1000,
71-
OverflowStrategy.dropNew
71+
OverflowStrategy.dropHead
7272
)
7373
}))
7474
}

backend/app/models/BaseFormat.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ object BaseFormat {
9494

9595
case JsString(url) =>
9696
try {
97-
JsSuccess(new URL(url))
97+
JsSuccess(URI.create(url).toURL)
9898
} catch {
9999
case e: Throwable =>
100100
JsError(s"couldn't parse url:$url, $e")

backend/build.sbt

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import play.sbt.routes.RoutesKeys
22
import org.scalafmt.sbt.ScalafmtPlugin.autoImport.*
3+
import com.typesafe.sbt.packager.docker.{DockerPermissionStrategy, Cmd, ExecCmd}
34

45
name := """lasius"""
56

67
Global / onChangedBuildSource := ReloadOnSourceChanges
78

89
lazy val root = (project in file("."))
9-
.enablePlugins(PlayScala,
10-
BuildInfoPlugin,
11-
SwaggerPlugin,
12-
AutomateHeaderPlugin)
10+
.enablePlugins(
11+
PlayScala,
12+
BuildInfoPlugin,
13+
SwaggerPlugin,
14+
AutomateHeaderPlugin,
15+
JavaAppPackaging, // Enables basic packaging
16+
DockerPlugin, // Enables Docker image generation
17+
AshScriptPlugin // Generates ash-compatible scripts for Alpine
18+
)
1319
.settings(
1420
RoutesKeys.routesImport += "binders.Binders._",
1521
swaggerV3 := true,
@@ -105,6 +111,27 @@ Production / javaOptions.withRank(
105111

106112
scalafmtOnCompile := true
107113

114+
// ============================================================================
115+
// Compiler Warning Suppressions
116+
// ============================================================================
117+
118+
// Suppress jsObjectWrites deprecation warnings from ReactiveMongo
119+
// Context:
120+
// - Using play2-reactivemongo 1.1.0-play30.RC17 (no stable release available)
121+
// - ReactiveMongo's jsObjectWrites is deprecated since 0.20.6 with message:
122+
// "Will be removed when provided by Play-JSON itself"
123+
// - This is an internal compatibility layer in the library, not our code
124+
// - Upgrading won't help - waiting for Play-JSON to provide native support
125+
// Adverse effects: NONE
126+
// - Only suppresses warnings in library code (reactivemongo package)
127+
// - Does NOT suppress deprecation warnings in our own code
128+
// - Does NOT hide actual problems in our codebase
129+
// - Can be removed when Play-JSON provides native support
130+
// Alternative: Wait for stable play2-reactivemongo release (not yet available)
131+
scalacOptions ++= Seq(
132+
"-Wconf:cat=deprecation&origin=reactivemongo\\..*&msg=jsObjectWrites:s"
133+
)
134+
108135
headerLicense := Some(
109136
HeaderLicense.Custom(
110137
"""|
@@ -127,3 +154,74 @@ headerLicense := Some(
127154
|along with Lasius. If not, see <https://www.gnu.org/licenses/>.
128155
|""".stripMargin
129156
))
157+
158+
// ============================================================================
159+
// Docker Configuration (sbt-native-packager)
160+
// ============================================================================
161+
162+
// Base image - Eclipse Temurin 25 JRE on Alpine for small size
163+
dockerBaseImage := "eclipse-temurin:25-jre-alpine"
164+
165+
// Exposed ports
166+
dockerExposedPorts := Seq(9000)
167+
168+
// Docker repository
169+
dockerRepository := Some("tegonal")
170+
171+
// Override package name for Docker (CI/CD expects "lasius-backend")
172+
Docker / packageName := "lasius-backend"
173+
174+
// Sanitize version for Docker tags (Docker doesn't allow '+' characters)
175+
// Preserve 'v' prefix to match frontend tagging convention
176+
import com.typesafe.sbt.packager.docker.DockerAlias
177+
dockerAlias := {
178+
val rawVersion = version.value.replace('+', '-')
179+
// sbt-dynver strips 'v' prefix, so add it back if not present
180+
val sanitizedVersion = if (rawVersion.startsWith("v")) rawVersion else s"v$rawVersion"
181+
DockerAlias(
182+
registryHost = dockerRepository.value,
183+
username = None,
184+
name = (Docker / packageName).value,
185+
tag = Some(sanitizedVersion)
186+
)
187+
}
188+
189+
// Only tag as 'latest' for stable semver releases (e.g., v1.2.3 or 1.2.3)
190+
// Any other tag pattern is considered beta/experimental
191+
dockerAliases ++= {
192+
val ver = version.value
193+
// Match semantic versioning: optional 'v' prefix + X.Y.Z (where X, Y, Z are numbers)
194+
val semverPattern = """^v?\d+\.\d+\.\d+$""".r
195+
val isStableRelease = semverPattern.pattern.matcher(ver).matches()
196+
197+
if (isStableRelease) {
198+
Seq(dockerAlias.value.withTag(Some("latest")))
199+
} else {
200+
Seq.empty
201+
}
202+
}
203+
204+
// Use multi-stage build for better security and smaller images
205+
dockerPermissionStrategy := DockerPermissionStrategy.MultiStage
206+
207+
// Run as non-root user (security best practice)
208+
// Default user is "demiourgos728" with UID 1001 (provided by sbt-native-packager)
209+
210+
// Note: No logs directory needed - using STDOUT logging (Docker best practice)
211+
212+
// Docker labels (including git commit info for traceability)
213+
dockerLabels := {
214+
val gitCommit = sys.env.getOrElse("COMMIT_SHORT_SHA",
215+
scala.sys.process.Process("git rev-parse --short=8 HEAD").!!.trim)
216+
217+
Map(
218+
"maintainer" -> "Tegonal Genossenschaft <https://tegonal.com>",
219+
"org.opencontainers.image.title" -> "Lasius Backend",
220+
"org.opencontainers.image.description" -> "Open source time tracker for teams",
221+
"org.opencontainers.image.vendor" -> "Tegonal Genossenschaft",
222+
"org.opencontainers.image.licenses" -> "AGPL-3.0",
223+
"org.opencontainers.image.source" -> "https://github.com/tegonal/lasius",
224+
"org.opencontainers.image.revision" -> gitCommit,
225+
"git-commit" -> gitCommit // Legacy label for backwards compatibility
226+
)
227+
}

backend/conf/logback-prod.xml

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,15 @@
33
<!--
44
~ Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
55
-->
6-
<!-- The default logback configuration that Play uses if no other configuration is provided -->
6+
<!-- Production logback configuration - STDOUT-only (Docker best practice) -->
77
<configuration>
8-
9-
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
10-
<file>${application.home}/logs/application.log</file>
11-
<encoder>
12-
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
13-
</encoder>
14-
</appender>
158

169
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
1710
<encoder>
1811
<pattern>%highlight(%-5level) %logger{15} - %message%n%xException{10}</pattern>
1912
</encoder>
2013
</appender>
2114

22-
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
23-
<appender-ref ref="FILE" />
24-
</appender>
25-
2615
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
2716
<appender-ref ref="STDOUT" />
2817
</appender>
@@ -42,7 +31,7 @@
4231
<logger name="actors.MyActor" level="ERROR" />
4332

4433
<root level="ERROR">
45-
<appender-ref ref="ASYNCFILE" />
34+
<!-- Only use STDOUT in production (Docker best practice) -->
4635
<appender-ref ref="ASYNCSTDOUT" />
4736
</root>
4837

backend/formatsources.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
sbt scalafmtAll scalafmtSbt
2-
prettier --write "{app,public}/**/*.{ts,scss,json,html}"
1+
sbt scalafmtAll scalafmtSbt

0 commit comments

Comments
 (0)