12

I have a Docker file like the following:

FROM openjdk:8

ADD . /usr/share/app-name-tmp

WORKDIR /usr/share/app-name-tmp

RUN ./gradlew build \
    mv ./build/libs/app-name*.jar /usr/share/app-name/app-name.jar

WORKDIR /usr/share/app-name

RUN rm -rf /usr/share/app-name-tmp

EXPOSE 8080

RUN chmod +x ./docker-entry.sh

ENTRYPOINT [ "./docker-entry.sh" ]

The problem is that the final image size is 1.1GB, I know it happens because gradle downloads and stores all the dependencies. What is the best way to remove those unnecessary files and just keep the jar?

iamdeit
  • 5,485
  • 4
  • 26
  • 40

6 Answers6

7

I am really confused about your image size. I have typical Spring Boot applications offering a REST service including an embedded servlet container in less than 200MB! It looks like your project dependencies can and should be optimised.

Docker Image

The openjdk:8 (243MB compressed) can be replaced by one with a reduced Alpine unix image like openjdk:8-jdk-alpine (52MB) as a base image but if you don't need compiler capabilities (e.g. don't use JSPs) you may also go for openjdk:8-jre-alpine (42MB) which includes the runtime only, have a look into Docker Hub. I use that for Spring Boot based REST services working great.

Java Dependencies

The Java dependencies needed for compile and runtime have to be included but you may have unused dependencies included:

  • check your dependencies, are the current compile/runtime dependencies really used or maybe can be removed or moved to test, see Gradle Java Plugin
  • some dependencies have a lot of transitive dependencies (display using gradle dependencies), check out for unnecessary ones and exclude them if unused, see Gradle Dependency Management. Be sure to do integration tests before applying finally, some transitive dependencies are not well documented but may be essential!
Arne Burmeister
  • 20,046
  • 8
  • 53
  • 94
  • 1
    If I execute ./gradlew build, the resulting jar is only 95MB, but the image size is around 1GB – iamdeit Dec 04 '16 at 20:55
  • Why you are running gradle from docker and what is in the directories you add? I use gradle docker plugin and only add the jar to the image calling docker from gradle and not the other way around – Arne Burmeister Dec 04 '16 at 22:29
7

With Docker 17.05+ you can use multi-stage builds.

"With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image."

So your Dockerfile could look like this:

#
# first stage (build)
#
FROM openjdk:8 as build

ADD . /usr/share/app-name-tmp

WORKDIR /usr/share/app-name-tmp

RUN ./gradlew build && \
    mv ./build/libs/app-name*.jar /usr/share/app-name/app-name.jar

#
# second stage. use alpine to reduce the image size
#
FROM openjdk:8-jre-alpine

WORKDIR /usr/share/app-name

COPY --from=build /usr/share/app-name/app-name.jar .

EXPOSE 8080

RUN chmod +x ./docker-entry.sh

ENTRYPOINT [ "./docker-entry.sh" ]

This way you only keep the jar and all the unnecessary files are not included in the final image.

Guido
  • 46,642
  • 28
  • 120
  • 174
5

Each RUN instruction creates a new layer on top of the existing file system. So the new layer after RUN instruction that deletes you app-name-tmp directory just masks the previous layer containing the downloaded libraries. Hence your docker image still has that size from all the layers built.

Remove the separate RUN rm -rf /usr/share/app-name-tmp instruction and include it in the same RUN instruction that does gradle build as shown below.

RUN ./gradlew build \
    mv ./build/libs/app-name*.jar /usr/share/app-name/app-name.jar \
    rm -rf /usr/share/app-name-tmp/*

So, your final Dockerfile would be

FROM openjdk:8

ADD . /usr/share/app-name-tmp
WORKDIR /usr/share/app-name-tmp

RUN ./gradlew build \
    mv ./build/libs/app-name*.jar /usr/share/app-name/app-name.jar \
    rm -rf /usr/share/app-name-tmp/*

WORKDIR /usr/share/app-name

EXPOSE 8080
RUN chmod +x ./docker-entry.sh
ENTRYPOINT [ "./docker-entry.sh" ]

The image built will still add up size from the directory /usr/share/app-name-tmp.

YYY
  • 6,107
  • 1
  • 12
  • 14
  • The base image could be contributing a little bit, but this is the real answer. Unfortunately, [Docker has not addressed the "nested builds"](https://github.com/docker/docker/issues/7115). The way I have generally dealt with this is having separate "build" and "runtime" images to handle the Docker limitations. See [this article](https://www.fpcomplete.com/blog/2015/12/docker-split-images) for a possible way of dealing with the problem. – mkobit Dec 04 '16 at 16:55
  • @mkobit you can now use multistage builds to address that issue https://docs.docker.com/engine/userguide/eng-image/multistage-build/ – Guido Sep 14 '17 at 21:05
  • 1
    @GuidoGarcía good point! Could be a good opportunity for a new answer or to improve upon this one. – mkobit Sep 15 '17 at 15:55
2

It seems your image comes from

FROM openjdk:8

so from

https://github.com/docker-library/openjdk/blob/e6e9cf8b21516ba764189916d35be57486203c95/8-jdk/Dockerfile

and in fact a Debian

FROM buildpack-deps:jessie-scm

you should try to use an Alpine base

https://github.com/docker-library/openjdk/blob/9a0822673dffd3e5ba66f18a8547aa60faed6d08/8-jdk/alpine/Dockerfile

I guess your image will be at least half the size

user2915097
  • 30,758
  • 6
  • 57
  • 59
  • I know about Alpine, but I want to keep using the 'openjdk:8' image. It is ~ 650MB but my application is adding around 500MB more in the ./gradlew build step. – iamdeit Dec 04 '16 at 11:43
1

Is this the container you deploy to production? If so, don't use it for the actual build. Do the build (and the testing) elsewhere and once it is blessed, copy just the JAR to your Docker production container.

Jamie Bisotti
  • 2,605
  • 19
  • 23
1

For OpenJDK-12

My application is written in Kotlin along with spring boot and maven.

I had same issue with openJDK-12 and OracleOpenJDK-12 size is 470 MB.

I wanted to reduce my container size so i selected adoptopenjdk/openjdk12:x86_64-alpine-jre-12.33 and achieved 189 MB as shown below.

FROM adoptopenjdk/openjdk12:x86_64-alpine-jre-12.33
RUN mkdir /app
COPY ./target/application-SNAPSHOT.jar /app/application-SNAPSHOT.jar
WORKDIR /app
CMD ["java", "-jar", "application-SNAPSHOT.jar"]

My final container size is 189MB (34 MB Application Jar size + 155 MB Base image size.)

Shree Prakash
  • 2,052
  • 2
  • 22
  • 33