sbt-docker pluginとTravisCIを使って、play2で作ったアプリをDockerHubにpushしてみよう！

## 1. やりたいこと

Play2で作成したアプリケーションをAWSとかGCPに配布したい。
でも、毎度毎度インスタンスにscalaの実行環境をプロビジョニングしたくない。。
Dockerのイメージを作成して、簡単にデプロイングがしたい。

## 2. 成果物

https://hub.docker.com/r/letusfly85/play-sample-auth
https://github.com/letusfly85/scala-oauth2-provider-example-skinny-orm

## 3. 手順

### 3-1. sbtのプロジェクトで、build.sbtを編集し、sbt-docker pluginを有効にする

[sbt-docker](https://github.com/marcuslonnberg/sbt-docker) のREADME.mdを見ながら設定をしました。
下記のような設定になりました。

```project/plugins.sbt
scalacOptions ++= Seq("-deprecation", "-language:_", "-unchecked")

addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.0")

addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.3.0")
```

```build.sbt
lazy val root = (project in file("."))
.enablePlugins(PlayScala, sbtdocker.DockerPlugin)
.settings(
  scalaVersion := "2.11.7",
  scalacOptions := Seq("-language:_", "-deprecation", "-unchecked", "-feature", "-Xlint"),
  javaOptions := Seq("-Dplay.evolutions.db.default.autoApply=true"),
  transitiveClassifiers in Global := Seq(Artifact.SourceClassifier),
  sources in (Compile, doc) := Nil,
  publishArtifact in (Compile, packageDoc) := false,
  parallelExecution in Test := false
).settings(
  resolvers += Resolver.file(
    "local-ivy-repos", file(Path.userHome + "/.ivy2/local")
  )(Resolver.ivyStylePatterns),
  libraryDependencies ++= Seq(
    jdbc,
    evolutions,
    "org.skinny-framework" %% "skinny-orm" % "2.0.5",
    "org.scalikejdbc" %% "scalikejdbc-play-dbapi-adapter" % "2.4.3",
    "com.nulab-inc" %% "play2-oauth2-provider" % "0.16.1"
  )
)

dockerfile in docker := {
  val appDir = stage.value
  val targetDir = "/app"

  new Dockerfile {
    from("java")
    entryPoint(s"$targetDir/bin/${executableScriptName.value}")
    copy(appDir, targetDir)
  }
}

imageNames in docker := Seq(
  ImageName("letusfly85/play-sample-auth:latest")
)

routesGenerator := InjectedRoutesGenerator
```



### 3-2. .travis.ymlを編集して、`docker login` 及び、`sbt dockerBuildAndPush`を実行させる

```.travis.yml
language: scala
scala:
  - 2.11.7

services:
  - docker

script:
  - sbt clean compile
  - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
  - sbt dockerBuildAndPush
```

### 3-3. 任意のインスタンスで、docker runをして、アプリケーションが利用可能か確認する

```bash
docker run -i -p 80:9000 -e "JAVA_OPTS=-Dplay.evolutions.db.default.autoApply=true" letusfly85/play-sample-auth
```

環境変数として、JAVA_OPTSを渡しているのは、これがこのサンプルアプリで必要な仕様だからです＾＾；

また、build.sbtの中で、ENTRYPOINTを指定しているので、`docker run`する際に、コマンドは不要になります。

結果以下のように出力されました。

```bash
docker run -i -p 80:9000 -e "JAVA_OPTS=-Dplay.evolutions.db.default.autoApply=true" letusfly85/play-sample-auth
08:12:53,091 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
08:12:53,091 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
08:12:53,091 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/app/conf/logback.xml]
08:12:53,091 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs multiple times on the classpath.
08:12:53,091 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [jar:file:/app/lib/root.root-0.1-SNAPSHOT.jar!/logback.xml]
08:12:53,091 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [file:/app/conf/logback.xml]
08:12:53,186 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set
08:12:53,187 |-INFO in ch.qos.logback.core.joran.action.ConversionRuleAction - registering conversion word coloredLevel with class [play.api.Logger$ColoredLevel]
08:12:53,187 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
08:12:53,192 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
08:12:53,211 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
08:12:53,235 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [play] to INFO
08:12:53,235 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [application] to DEBUG
08:12:53,235 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to ERROR
08:12:53,235 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[ROOT]
08:12:53,236 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
08:12:53,237 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@6ab778a - Registering current configuration as safe fallback point

[info] - application - Creating Pool for datasource 'default'
[info] - play.api.db.DefaultDBApi - Database [default] connected at jdbc:h2:mem:play
[info] - play.api.Play - Application started (Prod)
[info] - play.core.server.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
```

結果、ウェブブラウザアクセスもできました。

## 4. 所感

あとは、クラウドベンダーお手製のdocker管理ツール等と組み合わせたり、daemonスクリプトやsystemctlスクリプトをちょっと用意すれば利用できなくもなさそうな印象です。

できないこともないかなー、といった印象ですね＾＾

---

本日は以上となります。