Preparations
First we need to create Spring Boot project for tests. I will use Spring Boot CLI and single-Groovy-file project for this. Let's "clone" application from this tweet:Now if you will run
spring jar app.jar app.groovyYou will get fat app.jar with your entire project. What is the size for this jar? 17Mb. Why? Because it's fat and contains whole Spring Boot stack, including Spring Framework, Groovy and even embedded Tomcat! Now we can pack it with Docker:
What's wrong with it? Let's build an image:
$ docker build . Sending build context to Docker daemon 34.87 MB Sending build context to Docker daemon Step 0 : FROM java:8-jre ---> 028f36974b77 Step 1 : ADD app.jar /app/ ---> f39fac8b6c8d Removing intermediate container d3c168bb5b09 Step 2 : CMD java -jar /app/app.jar ---> Running in 3fbb5ba0cf4b ---> 51d0def78e12 Removing intermediate container 3fbb5ba0cf4b Successfully built 51d0def78e12Now change message inside your app.groovy file to "Hello dockerized world!" and pack it again with "spring jar" command above. If you will build your Docker image one more time than it will create a new layer the same size - 17Mb. And every time you need to change something in your project you will create a layer with all dependencies.
Exploded Jar? WAT?
Did you know that you can explode your fat jar and still can run it? Just try yourself, it works with any Spring Boot-based fat jar:$ unzip -q app.jar -d app $ java -cp app org.springframework.boot.loader.JarLauncher --server.port=8081 . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.2.3.RELEASE) ... 2015-04-12 13:22:44.484 INFO 6379 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081 (http) 2015-04-12 13:22:44.486 INFO 6379 --- [ main] .b.c.j.PackagedSpringApplicationLauncher : Started PackagedSpringApplicationLauncher in 2.706 seconds (JVM running for 3.078)How it can help us? Answer is inside an exploded directory:
$ ls -la app total 24 drwxr-xr-x 7 bsideup staff 238 Apr 12 13:21 . drwxr-xr-x 6 bsideup staff 204 Apr 12 13:21 .. -rw-r--r-- 1 bsideup staff 75 Apr 12 12:38 Dockerfile drwxr-xr-x 3 bsideup staff 102 Apr 12 12:38 META-INF -rw-r--r-- 1 bsideup staff 5136 Apr 12 12:38 ThisWillActuallyRun.class drwxr-xr-x 37 bsideup staff 1258 Apr 12 12:38 lib drwxr-xr-x 3 bsideup staff 102 Apr 12 12:38 orgDo you see this "lib" folder? It contains all your project dependencies. They are not changing often. So why not to cache them?
Docker caching
How to cache something in Docker? Just "ADD" it before the main files. Modify your Dockerfile with this changes:And build your image:
$ rm -rf app/ && unzip -q app.jar -d app $ docker build . Sending build context to Docker daemon 34.87 MB Sending build context to Docker daemon Step 0 : FROM java:8-jre ---> 028f36974b77 Step 1 : ADD app/lib/ /app/lib/ ---> 01fd4225441d Removing intermediate container 77d555817fa8 Step 2 : ADD app /app/ ---> 846a92bd6fc3 Removing intermediate container 86b26a48efde Step 3 : CMD java -cp /app/ org.springframework.boot.loader.JarLauncher ---> Running in 2e677531c0e5 ---> e90f469994c7 Removing intermediate container 2e677531c0e5 Step 4 : EXPOSE 8080 ---> Running in f05d3868f6ed ---> 526ff5ad98ae Removing intermediate container f05d3868f6ed Successfully built 526ff5ad98aeNow on Step 1 Docker will create a layer with your libraries and cache it until they will be changed.
Not so fast
UPDATE: since this fix https://github.com/spring-projects/spring-boot/issues/2807 future steps are not required anymore if you're using Spring Boot 1.3.0 or newer.
What will happen if you will run "spring jar" command again and then build an image? Depends on your Docker knowledge. Lets try:
$ spring jar app.jar app.groovy $ rm -rf app/ && unzip -q app.jar -d app $ docker build . Sending build context to Docker daemon 34.87 MB Sending build context to Docker daemon Step 0 : FROM java:8-jre ---> 028f36974b77 Step 1 : ADD app/lib/ /app/lib/ ---> 3270477dd01f Removing intermediate container 08a0ab7ed4ef Step 2 : ADD app /app/ ---> f9372f874d47 Removing intermediate container 751de4852196 Step 3 : CMD java -cp /app/ org.springframework.boot.loader.JarLauncher ---> Running in 4954fb8d37d3 ---> e2fc978f2740 Removing intermediate container 4954fb8d37d3 Step 4 : EXPOSE 8080 ---> Running in 9f70e24d5bc0 ---> 75999f39321b Removing intermediate container 9f70e24d5bc0 Successfully built 75999f39321b
This was my first reaction on happened. But then I realized that timestamp for libraries was changed after packaging. Can we fix it? Yes we can!
$ find ./app/lib/ | xargs touch -t 0000000000.00 $ docker build . Sending build context to Docker daemon 34.87 MB Sending build context to Docker daemon Step 0 : FROM java:8-jre ---> 028f36974b77 Step 1 : ADD app/lib/ /app/lib/ ---> 8028a9ba2b7b Removing intermediate container 52d21772824a Step 2 : ADD app /app/ ---> 7ed806b0089f Removing intermediate container f070b31695bf Step 3 : CMD java -cp /app/ org.springframework.boot.loader.JarLauncher ---> Running in 9a84ec5214ae ---> 8fda50a60f87 Removing intermediate container 9a84ec5214ae Step 4 : EXPOSE 8080 ---> Running in 8543d0799c68 ---> 06614c96e115 Removing intermediate container 8543d0799c68 Successfully built 06614c96e115Now every new build will use cache for libraries:
$ spring jar app.jar app.groovy $ rm -rf app/ && unzip -q app.jar -d app $ find ./app/lib/ | xargs touch -t 0000000000.00 $ docker build . Sending build context to Docker daemon 34.87 MB Sending build context to Docker daemon Step 0 : FROM java:8-jre ---> 028f36974b77 Step 1 : ADD app/lib/ /app/lib/ ---> Using cache ---> 8028a9ba2b7b Step 2 : ADD app /app/ ---> e0b404447dbe Removing intermediate container 8952413133a9 Step 3 : CMD java -cp /app/ org.springframework.boot.loader.JarLauncher ---> Running in 8c2e55128dc0 ---> 30f8fb7f4eba Removing intermediate container 8c2e55128dc0 Step 4 : EXPOSE 8080 ---> Running in b08967c7a454 ---> 9e028bbfef90 Removing intermediate container b08967c7a454 Successfully built 9e028bbfef90
Now your services are real microservices.