0

Am new to docker and am trying out containerization for the first time.

Have seen Java projects on Github that developers have setup as both gradle and maven projects.

Created the following project structure:

HelloDocker
│
├── Dockerfile
├── README.md
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── mvnw
├── mvnw.cmd
├── pom.xml
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── hellodocker
    │   │           ├── HelloDockerApplication.java
    │   │           ├── controller
    │   │           │   └── HelloController.java
    │   │           └── model
    │   │               └── User.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── com
                └── hellodocker
                    └── HelloDockerApplicationTests.java

Maven pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.hellodocker</groupId>
    <artifactId>hellodocker</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hellodocker</name>
    <description>Spring Boot Docker Example</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

build.gradle:

plugins {
    id 'org.springframework.boot' version '2.2.6.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

group = 'com.hellodocker'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

test {
    useJUnitPlatform()
}

Dockerfile:

FROM openjdk:11
VOLUME /tmp
ADD target/hellodocker-0.0.1-SNAPSHOT.jar hellodocker-app.jar
ENTRYPOINT ["java", "-jar", "/hellodocker-app.jar" ]

As you can see, this relies on the project being built into a jar file from maven (because of the target directory dependency on line 3 (gradle builds rely/depend on a build/lib directory.


Question(s):

  1. Is possible to set up the Dockerfile to accommodate both types of build scripts?

  2. Is it a bad practice (e.g. for from a DevOps continuous integration standpoint) to rely on two build script? For example, should Java projects that will be deployed on CircularCI, Jenkins, Travis and / or GitLab rely only one type of build script DSL?

The reason I wish to use both is so future developers who access my projects can be comfortable with choosing either gradle or maven (of course, this creates the hassle of maintaining two different build scripts, in the future)...

PacificNW_Lover
  • 4,746
  • 31
  • 90
  • 144

3 Answers3

0

This seems a bad practice to have both Maven & Gradle since you have to keep consistency in used dependencies versions in both.

As for Dockerfile, it will be the same for both since Dockerfile just takes an artifact that was built by any of those.

Mikhail Kopylov
  • 2,008
  • 5
  • 27
  • 58
  • how would it be the same for both (when it comes to the Dockerfile)? Maven creates a ```target``` folder (with the jar placed directly underneath it), whereas Gradle creates the directory structure of ```build/lib``` with the jar located directly under ```lib```. So how does that make the Dockerfile the same? There's no way to create like an if statement inside the Dockerfile? – PacificNW_Lover May 03 '20 at 04:41
  • @PacificNW_Lover Yeah, I see what you mean. You can configure to use a same directory for both tools. See https://stackoverflow.com/a/6690662/1728511 to configure Maven for example If two Dockerfiles have the only difference in some file path, it's worth unifying the path rather than having two pretty much duplicate Dockerfiles. – Mikhail Kopylov May 03 '20 at 04:48
0

Choice is the nemesis of decision, and it seems you are loosing this battle. Pick one build tool which everyone can easily grasp and structure your CI/CD pipeline around this tool. I don't see having both as helping anyone; it seems more confusing than helpful.

Most CI/CD tools are tuned to automatically detect what kind of project you are building and run the most basic commands to get accomplish the testing goal of CI.

This means that in gradle projects, they will usually run gradle check, and for maven, it is usually mvn test.

Having both in the same project leads to confusion both for the CI tool and for your team, and eventually one of them will become bloat in your project because everyone would have gravitated towards one build system they all like/understand.


As for your question about Docker, you should start with an image that is already built for one of the tools, and then install the other tool inside this new image. I would start with the gradle image and then apk add maven to get maven as well.

smac89
  • 39,374
  • 15
  • 132
  • 179
0

Mechanically, there is only one path through the Dockerfile and it doesn't support conditionals, outside of shell conditionals in single RUN commands. You can't conditionally COPY a file based on whether it exists or not, for example.

As the other answers to this question suggest, I'd pick one way of building the image, and I'd probably go so far as to delete the other build system's control files. This avoids problems where the official builds use Maven but the one developer who uses Gradle didn't update the POM file. If you still need those files, they'll be in source control history.

If you really can't choose, you can hack around this with a multi-stage build...but it's pretty hacky. (Notice that the shell logic itself to try to find the jar file on its own is twice as long as the entire Dockerfile you had previously.)

# First stage just finds the jar file wherever it may be.
FROM busybox AS staging
WORKDIR /staging

# Copy the _entire_ build context here.
COPY ./ ./

# Now we can look for the jar file using shell commands.
RUN if [ -f target/hellodocker-0.0.1-SNAPSHOT.jar ]; then \
      cp target/hellodocker-0.0.1-SNAPSHOT.jar /hellodocker-app.jar; \
    else if [ -f build/lib/hellodocker-0.0.1-SNAPSHOT.jar ]; then \
      cp build/lib/hellodocker-0.0.1-SNAPSHOT.jar /hellodocker-app.jar; \
    else \
      echo "Could not find jar file" >&2; \
      exit 1; \
    fi

# This is the actual image, which is essentially what you had before.
FROM openjdk:11
COPY --from=staging /hellodocker-app.jar /hellodocker-app.jar
CMD ["java", "-jar", "/hellodocker-app.jar"]
David Maze
  • 130,717
  • 29
  • 175
  • 215