Running Play in an alpine-based Docker container


#1

Has anyone had success running Play on an alpine-based Docker container? It seems broken per https://github.com/playframework/playframework/issues/8282.


(Rich Dougherty) #2

Sounds like a useful thing to get working! I personally haven’t had any experience with that. It sounds like Play or sbt-native-packager might need a patch. ;)


#3

I got it working with the below in build.sbt, by inspecting the docker commands via show dockerCommands in sbt and then duplicating it, with the addition of adding bash.

Now, the point of these minimal Alpine releases is that they don’t have bash :) e.g. when there was the “ShellShock” bash vulnerability, these images were unaffected because they don’t have bash… that’s desirable.

It would be great to support these images out of the box. Is this a Play issue or a sbt-package-manager issue?

Update: Also, the docker image size (with just the base image and a minimal Play app) is something like 859 MB for the standard image and 229 MB for the alpine-based one. Big difference.

import com.typesafe.sbt.packager.docker._

dockerCommands := Seq(
  Cmd("FROM", "openjdk:8-jre-alpine"),
  Cmd("RUN", "apk --no-cache add bash"),
  Cmd("WORKDIR", "/opt/docker"),
  Cmd("ADD", "--chown=daemon:daemon opt /opt"),
  Cmd("USER", "daemon"),
  Cmd("ENTRYPOINT", """["bin/foo-service"]"""),
  Cmd("CMD", """[]""")
)

(Rich Dougherty) #4

If you want this recipe to be easily reusable you write a small sbt plugin that basically just adds these settings to a Play project. Here’s how to write a plugin: https://www.scala-sbt.org/1.0/docs/Plugins.html. Then users who want to use it would just need to add your plugin to their projects!


(Schmitt Christian) #5

Actually using alpine based images is not the best idea for JRE stuff.
It’s better to use sbt-assembly and use gcr.io/distroless/java.

Something like that:

FROM MY_SBT_BUILD_IMAGE
ARG JAVA_OPTS
ADD . /app
WORKDIR /app
RUN /usr/local/sbt/bin/sbt server/assembly
RUN mkdir -p /out && mv server/target/scala-2.12/server.jar /out

FROM gcr.io/distroless/java
COPY --from=build-env /out /app
WORKDIR /app
EXPOSE 9000
CMD ["server.jar"]

#6

I consider what I did a hack - if the underlying docker commands change in sbt-package-manager, the above code (or plugin) would not reflect that.

It would be great to get Play apps running on a stock Alpine-based docker image. This means looking into the runscript generation. I think this means there’s an issue here: https://github.com/sbt/sbt-native-packager/blob/master/src/main/scala/com/typesafe/sbt/packager/archetypes/scripts/AshScriptPlugin.scala


#7

Could you please expand on why (using alpine based images is not good with JRE)? There are official alpine-based ‘openjdk’ docker images… Doing a brief search, I see a number of posts which recommend this route:

https://medium.com/@hudsonmendes/docker-spring-boot-choosing-the-base-image-for-java-8-9-microservices-on-linux-and-windows-c459ec0c238]


(Schmitt Christian) #8

Well first of all alpine still comes with tools you don’t need (package manager, etc.)

Second a docker image should be as small as possible. and a sbt-assembly image with distroless can be even smaller.

(Well alpine is fine, ok-ish and at least better than most options, but removing any distro is even better and mostly more secure)
(Also your last link basically does the same I did with distroless, just with jdk9 (which does not work that “good” with play (yet)))

Of course the best image would be some kind of image where you have the following layers:

  1. base image with jre
  2. image with play dependencies & your dependencies
  3. your application jar

of course this won’t work sbt-assembly but it will make your application even better deployable.
There are upsides and downsides of both approaches.
The first approach adopts for a overall “smallness”, while the second tries to minimize diff size if your deps won’t change.


(Rich Dougherty) #9

Can you raise an issue over there on that project?


(Schmitt Christian) #10

There is already an issue:

However it’s probably Play related.


(Schmitt Christian) #11

Actually one of the “best” ways at the moment is probably to just run play with the following under docker:

FROM gcr.io/distroless/java
COPY target/universal/stage/lib/* /app/lib/
COPY target/universal/stage/conf/ /app/conf/
WORKDIR /app
EXPOSE 9000
ENTRYPOINT ["java"]
CMD ["-Duser.dir=/app", "-cp", "conf/:lib/*", "play.core.server.ProdServerStart"]

create with:

sbt stage && docker build -t YOUR_TAG .

(it would probably be even more sane to actually have mutliple layer with your library, so that consider your application jars will be added after dependency jars, this makes your image bigger but it will use less space while upgrading, but I didn’t have time to fine tune that.)