Java on Docker

In this article you will learn about setting up a Java-based project on Codeship Pro. We will use Maven and Gradle for our build configuration, but the same concept applies for any other Java based language or tool.

Services and Steps

Before reading through the documentation please take a look at the Services and Steps documentation page so you have a good understanding how services and steps on Codeship work.

Dockerfile

We will start by creating Dockerfiles that lets you run your Java based test suite in Codeship with Maven and Gradle.

Please take a look at our Dockerfile Caching best practices article first to make sure you build your Dockerfile in a way that only invalidates the Docker cache when necessary.

These Dockerfiles will give you a good starting point to install any further packages or tools you might need. Take a look at our browser testing documentation to find and install any further tools you might need for your build.

Maven

Following is an example Dockerfile with inline comments describing each step in the file.

# We're using the official Maven 3 container from the Dockerhub (https://hub.docker.com/_/maven/).
# Take a look at the available versions so you can specify the Java version you want to use.
FROM maven:3

# INSTALL any further tools you need here so they are cached in the docker build

WORKDIR /app

# Copy the pom.xml into the container to install all dependencies
COPY pom.xml ./

# Run install task so all necessary dependencies are downloaded and cached in
# the Docker container. We're running through the whole process but disable
# testing and make sure the command doesn't fail.
RUN mvn install clean --fail-never -B -DfailIfNoTests=false

# Copy the whole repository into the container
COPY . ./

Gradle

# Starting from the Openjdk-8 container
FROM java:openjdk-8-jdk

# Set the WORKDIR. All following commands will be run in this directory.
WORKDIR /app

# Copying all gradle files necessary to install gradle with gradlew
COPY gradle gradle
COPY \
  build.gradle \
  gradle.properties \
  gradlew \
  settings.gradle \
  ./

# Install the gradle version used in the repository through gradlew
RUN ./gradlew

# Run gradle assemble to install dependencies before adding the whole repository
RUN gradle assemble

ADD . ./

codeship-services.yml

The following example will use the Dockerfile we created to set up a container we call project_name (please change to your specific project name) that will run your build. We’re adding a PostgreSQL container and Redis container so the tests have access to those two services.

When accessing other containers please be aware that those services do not run on localhost, but on a different hostname, e.g. “postgres” or “mysql”. If you reference localhost in any of your configuration files you have to change that to point to the hostname of the service you want to access. Setting them through environment variables and using those inside of your configuration files is the cleanest approach to setting up your build environment.

project_name:
  build:
    image: organisation_name/project_name
    dockerfile_path: Dockerfile
  # Linking Redis and Postgres into the container
  links:
    - redis
    - postgres
  # Set environment variables to connect to the service you need for your build.
  # Those environment variables can overwrite settings from your configuration
  # files if configured. Make sure that your environment variables and
  # configuration files work work together as expected.
  environment:
    - DATABASE_URL=postgres://postgres@postgres/YOUR_DATABASE_NAME
    - REDIS_URL=redis://redis
# Service definition that specify a version of the service through container tags
redis:
  image: redis:2.8
postgres:
  image: postgres:9.4

For more information about other services you can use with Codeship check out our services and databases documentation.

codeship-steps.yml

Now we’re going to set up our codeship-steps.yml file. Every step in a build gets its own clean container and linked services. Any setup commands that are necessary to setup a linked container (e.g. database migrations) need to be run for every step. While this duplicates some of the work, it also makes sure your parallelized test commands run completely independently.

Maven

- name: ci
  service: project_name
  command: mvn test -B

Gradle

- name: ci
  service: project_name
  command: gradle build

Parallelising your build

As a step can only have a single command the best way to set up the parallelised build is through scripts in your repository. You can then call those scripts in your step file.

The following step file calls a scripts/ci file that we will create in two parallelised steps. You can use it with Maven or Gradle.

- name: ci
  type: parallel
  steps:
  - service: project_name
    command: bash -c "CODESHIP_NODE_INDEX=0 ./scripts/ci"
  - service: project_name
    command: bash -c "CODESHIP_NODE_INDEX=1 ./scripts/ci"

Make sure to set the CODESHIP_NODE_TOTAL environment variable in the codeship-services.yml file.

environment:
  - DATABASE_URL=postgres://postgres@postgres/YOUR_DATABASE_NAME
  - REDIS_URL=redis://redis
  - CODESHIP_NODE_TOTAL=2

And here is the corresponding script file you can put into script/ci. It finds all files ending in Test.java and then splits them up between the parallel containers. You can use the same script file for Maven and Gradle, just make sure to change the command at the end of the following script that starts either Maven or Gradle.

#!/bin/bash

set -e

NODE_TOTAL=${CODESHIP_NODE_TOTAL:-1}
NODE_INDEX=${CODESHIP_NODE_INDEX:-0}

# Find files named Test.java in the repository and sort them. We're setting the Dockerfile as the randomisation source so the randomisation is repeatable. You can add any file instead of Dockerfile.
files=$(find ./ -name "*Test.java" | sort -R --random-source=Dockerfile)

i=0

tests=()
for file in $files
do
  if [ $(($i % ${NODE_TOTAL})) -eq ${NODE_INDEX} ]
  then
    test=`basename $file | sed -e "s/.java//"`
    tests+="${test},"
  fi
  ((i+=1))
done

mvn test -B -Dtest=${tests}
# gradle test --tests ${tests}