11import play .sbt .routes .RoutesKeys
22import org .scalafmt .sbt .ScalafmtPlugin .autoImport .*
3+ import com .typesafe .sbt .packager .docker .{DockerPermissionStrategy , Cmd , ExecCmd }
34
45name := """ lasius"""
56
67Global / onChangedBuildSource := ReloadOnSourceChanges
78
89lazy 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
106112scalafmtOnCompile := 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+
108135headerLicense := 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+ }
0 commit comments