There are a few different approaches to packaging Play applications into a Docker Image. You can create a Dockerfile file by hand, customize preexisting one or just by using some tool to generate it. Since Play comes with a great packaging application – sbt-native-packager – we will be using the last option.

Docker Image

In build.sbt we need to add:

import com.typesafe.sbt.packager.docker._ 
dockerChmodType := DockerChmodType.UserGroupWriteExecute 
dockerPermissionStrategy := DockerPermissionStrategy.CopyChown

This will set the correct file permissions inside our Docker Image.

Next step is to navigate to the project directory, open an sbt shell and run

docker:publishLocal

This command builds our local Docker image.

To check if the image was created we need to open a terminal and run docker images [APP_NAME] wheres [APP_NAME] is the name of your Play application. There should be a record representing our image with a TAG number same as the application version specified in application.conf.

After this we need to run the image with command:

docker run --rm -p 9000:9000 APP_NAME:TAG
  • --rm will delete container after exit
  • -p 9000:9000 maps port 9000 to containers port 9000

With this setup, your Play application will run with the same configuration file as your local version. Most of the time you will need a separate config for your production environment and for your local development. To achieve this we will:

  1. Copy application.conf and rename it to prod.conf
  2. Remove version information from the production config file
  3. Addinclude "application.conf" on top of the production file

This will allow us to override some settings in the production environment by editing prod.conf.

To run our Play application with the new configuration file we need to rebuild the docker image and rerun the container with a new image and one other parameter: -Dconfig.file=/opt/docker/conf/prod.conf. The full command should look similar to this:

 docker run -p 9000:9000 APP_NAME:TAG -Dconfig.file=/opt/docker/conf/prod.conf 

Volumes

To make changes in a production configuration file you will need to edit it in your local version of source code, rebuild the image and rerun the container. Although it works, it’s quite tedious. A better way would be to set up a Docker volume. To do this we need to copy the conf directory from your project to production and run your image but with an extra argument:

 docker run 
    -p 9000:9000 
    -v /full/path/to/conf/directory:/opt/docker/conf APP_NAME:TAG 
    -Dconfig.file=/opt/docker/conf/prod.conf

The -v /full/path/to/conf/directory:/opt/docker/conf will create a Docker volume which allows us to share files between host system and Docker container. Now if you make changes to your production configuration files you just need to rerun the Docker container.

Now that we can easily separate the production configuration from our development configuration we can simplify docker run. We can create anapplication.ini file in our production environment and put -Dconfig.file=/opt/docker/conf/prod.conf and other startup parameters to it. After this, we can run our image with this command:

 docker run 
    -p 9000:9000 
    -v /full/path/to/conf/directory:/opt/docker/conf 
    APP_NAME:TAG

Files

Most server applications need to handle file uploads. To allow saving files from Play application we need to crate a directory. To do that we need to add code below to build.sbt.

 dockerCommands ++= Seq(
    ExecCmd("RUN", "mkdir", 
        s"${(defaultLinuxInstallLocation in Docker).value}/APP_NAME_files"), 
)

This will add a command to the generated Dockerfile that will create APP_NAME_files directory in container.

On every restart, the Docker container gets rebuild, which means that all files that aren’t in Volumes are discarded, including files from a previously created directory. To prevent this we need to add another volume to docker run command. While we’re at it, we can add another volume for log files.

 docker run 
    -p 9000:9000 
    -v /full/path/to//conf:/opt/docker/conf 
    -v /full/path/to/files:/opt/docker/APP_NAME_files 
    -v /full/path/to/logs:/opt/docker/YOUR_LOG_DIR 
    APP_NAME:TAG

Tips and Tricks

By adding dockerUpdateLatest := true to your build.sbt you will always have access the latest build version of your image buy TAG latest

If you host your database on the same machine as your Docker container, you can access it from within the container using host.docker.internal or by pointing at your machines public IP.