Search code examples
scalasbtdockersbt-native-packager

Why is sbt-native-packager staging bin/start as a directory instead of a script?


I'm using sbt-native-packager and sbt-docker to generate Docker images as described in this tutorial. The Docker image won't run (permission denied), and on inspection it turns out that bin/start is being created as an empty directory.

dan@cobalt:~/projects/confabulous/deva$ ls -l target/universal/stage/bin/
total 24
-rwxrw-r-- 1 dan dan 11591 Aug  5 20:44 deva
-rw-rw-r-- 1 dan dan  6211 Aug  5 20:44 deva.bat
drwxrwxr-x 2 dan dan  4096 Dec 31  1969 start

It also has a null timestamp for some reason. Why is it being created as a directory and not a shell script?

Here's my plugins.sbt:

addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.4")

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.7.4")

addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.1")

addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "0.5.0")

resolvers += Classpaths.sbtPluginReleases

And here's my build.sbt:

import DockerKeys._
import sbtdocker.{Dockerfile, ImageName}
import com.typesafe.sbt.packager.Keys._

organization  := "com.confabulous"

name          := "deva"

version       := "0.0.1"

scalaVersion  := "2.10.3"

scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8", "-language:postfixOps")

resolvers ++= Seq(
  "sonatype releases"  at "https://oss.sonatype.org/content/repositories/releases/",
  "sonatype snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/",
  "typesafe repo"      at "http://repo.typesafe.com/typesafe/releases/"
)

libraryDependencies ++= Seq(
  "ch.qos.logback"      %   "logback-classic" % "1.0.9",
  "com.typesafe.akka"   %   "akka-slf4j_2.10" % "2.3.3",
  "com.typesafe.akka"   %%  "akka-actor"      % "2.3.3",
  "com.typesafe.akka"   %%  "akka-remote"     % "2.3.3",
  "com.typesafe.akka"   %%  "akka-agent"      % "2.3.3",
  "com.typesafe.slick"  %%  "slick"           % "2.0.1-RC1",
  "org.mozilla"         %   "rhino"           % "1.7R4",
  "org.postgresql"      %   "postgresql"      % "9.3-1101-jdbc3",
  "org.msgpack"         %%  "msgpack-scala"   % "0.6.8",
  "com.livestream"      %%  "scredis"         % "1.1.2",
  "com.confabulous"     %%  "messages"        % "0.0.1-SNAPSHOT",
  "com.confabulous"     %%  "db"              % "0.0.1-SNAPSHOT"
)

packageArchetype.java_server

sbtdocker.Plugin.dockerSettings

mappings in Universal += baseDirectory.value / "docker" / "start" -> "bin/start"

docker <<= docker.dependsOn(com.typesafe.sbt.packager.universal.Keys.stage.in(Compile))

// Define a Dockerfile
dockerfile in docker <<= (name, stagingDirectory in Universal) map {
    case (appName, stageDir) =>
        val workingDir = s"/opt/${appName}"
        new Dockerfile {
            // Use a base image that contain Java
            from("relateiq/oracle-java7")
            maintainer("Dan Ellis <dan@halftreelabs.com>")
            expose(1600)
            add(stageDir, workingDir)
            run("chmod",  "+x",  s"/opt/${appName}/bin/${appName}")
            run("chmod",  "+x",  s"/opt/${appName}/bin/start")
            workDir(workingDir)
            entryPointShell(s"bin/start", appName, "$@")
        }
}

imageName in docker := {
    ImageName(
        namespace = Some("confabulous.com"),
        repository = name.value
        //,tag = Some("v" + version.value))
    )
}

Solution

  • The referenced article is part 2 of a series, where the address of the container is passed into the Java program by a script. The script itself is referenced in part 1.

    mappings in Universal takes a sequence of (File, String) tuples. The File is copied to the path specified by the String in the resulting image.

    In this case, if there is no file present at baseDirectory.value / "docker" / "start", then nothing is available to copy, and the resulting is the behavior you describe.

    You should create an appropriate start script, as discussed in part 1.