How to use docker volume to deploy war / jar in Tomcat

18,438

Solution 1

You have two separate problems:

  1. Depending on what command you use, Maven might well remove and recreate your target directory, which will leave the old, removed target directory still opened for the volume mount, by the Docker process. Your old file will be removed, and the new file created into a new directory which Docker has no idea about.

  2. When Maven builds a new WAR ZIP file, your servlet runner might notice the new file mid-build, and try to open a half-baked WAR, which would of course end in failure.

I suggest that you create a separate, at least semi-permanent directory, not in the target tree, for the purpose of being mounted by Docker. Create a new Maven profile in your pom.xml file, and add a build target which copies your WAR file, after it's done being built, into that new directory, which you mount as Tomcat's webapps inside your container.

Edit: Here's a solution which doesn't depend on the particular manner in which the virtualization system used by Docker might implement volume file transfer on any particular platform.

https://git.mikael.io/mikaelhg/docker-tomcat-war-deploy-poc

pom.xml snippet:

        <plugin>
            <groupId>org.codehaus.cargo</groupId>
            <artifactId>cargo-maven2-plugin</artifactId>
            <version>1.6.8</version>
            <configuration>
                <container>
                    <containerId>tomcat8x</containerId>
                    <type>remote</type>
                </container>
                <configuration>
                    <type>runtime</type>
                    <properties>
                        <cargo.protocol>http</cargo.protocol>
                        <cargo.hostname>localhost</cargo.hostname>
                        <cargo.servlet.port>8080</cargo.servlet.port>
                        <cargo.remote.username>admin</cargo.remote.username>
                        <cargo.remote.password>admin</cargo.remote.password>
                    </properties>
                </configuration>
                <deployer>
                    <type>remote</type>
                </deployer>
                <deployables>
                    <deployable>
                        <groupId>${project.groupId}</groupId>
                        <artifactId>${project.artifactId}</artifactId>
                        <type>${project.packaging}</type>
                        <properties>
                            <context>/app</context>
                        </properties>
                    </deployable>
                </deployables>
            </configuration>
        </plugin>

docker-compose.yml snippet:

tomcat:
  image: tomcat:8
  volumes:
    - ./tomcat-users.xml:/usr/local/tomcat/conf/tomcat-users.xml
    - ./manager-context.xml:/usr/local/tomcat/webapps/manager/META-INF/context.xml
  ports:
    - "8080:8080"
  depends_on:
    - db

tomcat-users.xml:

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users>
    <role rolename="manager-gui"/>
    <role rolename="manager-script"/>
    <user username="admin" password="admin" roles="manager-gui,manager-script"/>
</tomcat-users>

manager-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Context antiResourceLocking="false" privileged="true">
    <Manager sessionAttributeValueClassNameFilter="java\.lang\.(?:Boolean|Integer|Long|Number|String)|org\.apache\.catalina\.filters\.CsrfPreventionFilter\$LruCache(?:\$1)?|java\.util\.(?:Linked)?HashMap"/>
</Context>

Then:

mvn package

mvn cargo:redeploy

Edit 2: As a response to "... is possible to do that without anz additional plugin?" in the comments:

Yes. If you:

  1. Are running Windows on the host, and a Tomcat Docker image inside a virtual machine.

  2. Want to accomplish this through the use of volumes and no extra plugins.

... you can go about it like this:

  1. Mount, for example, C:/example/wars to the Docker containers' /tmp/example/wars.

  2. Run mvn package.

  3. Copy the WAR file, say with a script which does the whole thing, from the build to the deploy, to the directory C:/example/wars. We are only taking this step because you might run mvn clean which will remove the target directory, and if you've mounted it directly, Docker might not notice the new target directory created by mvn.

  4. Look up your container name with docker ps.

  5. Run the command, again from your deploy script, docker exec $CONTAINER mv /tmp/example/wars/*.war /usr/local/tomcat/webapps/ which will copy, inside the Docker container, inside the virtual machine, the complete, non-broken WAR ZIP file into the deployment directory.

Solution 2

While this does NOT answer your question exactly, there is an alternative to consider that does not require you to have a different development configuration than for test or production.

Just build your war file locally and then docker cp it:

docker cp D:\myproj\target\app.war My_Tomcat_Container:/usr/local/tomcat/webapps/ROOT.war

Solution 3

Your problem seems simpler than it looks. First of all, yes you can do that, a pretty good and production grade example here: https://hub.docker.com/r/esystemstech/liferay

About your docker file, this line does nothing for you, besides hiding the files under a layer:

RUN rm -rvf /usr/local/tomcat/webapps/ROOT

which means this is not even saving space. Now, consider mounting the folder as your volume, instead of the file. You can also leave the context inside the file. and finally just check if the timing is correct, I mean, if you are rebooting after the packing operation. I am saying that because if:

Caused by: java.util.zip.ZipException: error in opening zip file

For what I am seeing in your examples, you do not even need a Dockerfile, you can replace the build with an image in your docker compose file, and just mount the volume. Unless you are doing extra stuff inside the docker file.

Share:
18,438

Related videos on Youtube

Denis Stephanov
Author by

Denis Stephanov

Updated on June 19, 2022

Comments

  • Denis Stephanov
    Denis Stephanov about 2 years

    Is it possible to deploy some java war or jar file in Tomcat? I looking for a lot of tutorials and the only solution I found is copy project war file into /usr/local/tomcat/webapps/.

    I actually used that solution but I would like to improve my dockerisation. My primary goal is when I run my 2 images (application in tomcat and db image) with docker-compose I want to use my local war file of target folder in tomcat, and, when I build war again after the code changed, that change will be reflected without stopping containers, removing, and rebuilding. Can you help to do that? My attempts failed. I want it just for development purpose.

    Here is my docker-compose.yml

    version: '3'
    
    services:
    
      tomcat-service:
        build:
          context: ../
          dockerfile: docker/app/Dockerfile
        volumes:
          - D:\myproj\target\app.war:/usr/local/tomcat/webapps/ROOT.war
        ports:
          - "8080:8080"
        depends_on:
          - "db-service"
    
      db-service:
        build: ./database
        ports:
          - "5433:5432"
    

    and Dockerfile for that tomcat

    FROM tomcat:8.0-jre8
    RUN rm -rvf /usr/local/tomcat/webapps/ROOT
    COPY ./docker/app/context.xml /usr/local/tomcat/conf/
    # with following copy command it works, but when I rebuild war file, I need stop docker-compose and build and run it again .. I want use volume instead of copy war
    #COPY ./pnp-web/target/pnp.war /usr/local/tomcat/webapps/ROOT.war
    EXPOSE 8080
    CMD ["catalina.sh", "run"]
    

    With configuration above applicaton starts, but when I run mvn clean package application is not loaded anymore

    EDIT

    I checked log of tomcat container and I found this error:

    tomcat-cont       | 10-Jul-2018 08:20:36.754 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive /usr/local/tomcat/webapps/ROOT.war
    tomcat-cont       | 10-Jul-2018 08:20:36.858 SEVERE [localhost-startStop-1] org.apache.catalina.core.ContainerBase.addChildInternal ContainerBase.addChild: start:
    tomcat-cont       |  org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[]]
    tomcat-cont       |     at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:162)
    tomcat-cont       |     at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:755)
    tomcat-cont       |     at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:731)
    tomcat-cont       |     at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717)
    tomcat-cont       |     at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:973)
    tomcat-cont       |     at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1850)
    tomcat-cont       |     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    tomcat-cont       |     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    tomcat-cont       |     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    tomcat-cont       |     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    tomcat-cont       |     at java.lang.Thread.run(Thread.java:748)
    tomcat-cont       | Caused by: org.apache.catalina.LifecycleException: Failed to start component [org.apache.catalina.webresources.StandardRoot@51f50cb1]
    tomcat-cont       |     at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:162)
    tomcat-cont       |     at org.apache.catalina.core.StandardContext.resourcesStart(StandardContext.java:5016)
    tomcat-cont       |     at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5149)
    tomcat-cont       |     at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)
    tomcat-cont       |     ... 10 more
    tomcat-cont       | Caused by: org.apache.catalina.LifecycleException: Failed to initialize component [org.apache.catalina.webresources.JarResourceSet@20e48a4a]
    tomcat-cont       |     at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:107)
    tomcat-cont       |     at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:135)
    tomcat-cont       |     at org.apache.catalina.webresources.StandardRoot.startInternal(StandardRoot.java:722)
    tomcat-cont       |     at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)
    tomcat-cont       |     ... 13 more
    tomcat-cont       | Caused by: java.lang.IllegalArgumentException: java.util.zip.ZipException: error in opening zip file
    tomcat-cont       |     at org.apache.catalina.webresources.AbstractSingleArchiveResourceSet.initInternal(AbstractSingleArchiveResourceSet.java:142)
    tomcat-cont       |     at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:102)
    tomcat-cont       |     ... 16 more
    tomcat-cont       | Caused by: java.util.zip.ZipException: error in opening zip file
    tomcat-cont       |     at java.util.zip.ZipFile.open(Native Method)
    tomcat-cont       |     at java.util.zip.ZipFile.<init>(ZipFile.java:225)
    tomcat-cont       |     at java.util.zip.ZipFile.<init>(ZipFile.java:155)
    tomcat-cont       |     at java.util.jar.JarFile.<init>(JarFile.java:166)
    tomcat-cont       |     at java.util.jar.JarFile.<init>(JarFile.java:130)
    tomcat-cont       |     at org.apache.tomcat.util.compat.JreCompat.jarFileNewInstance(JreCompat.java:170)
    tomcat-cont       |     at org.apache.tomcat.util.compat.JreCompat.jarFileNewInstance(JreCompat.java:155)
    tomcat-cont       |     at org.apache.catalina.webresources.AbstractSingleArchiveResourceSet.initInternal(AbstractSingleArchiveResourceSet.java:139)
    tomcat-cont       |     ... 17 more
    tomcat-cont       |
    tomcat-cont       | 10-Jul-2018 08:20:36.859 SEVERE [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Error deploying web application archive /usr/local/tomcat/webapps/ROOT.war
    tomcat-cont       |  java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].Stand
    ardContext[]]
    tomcat-cont       |     at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:759)
    tomcat-cont       |     at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:731)
    tomcat-cont       |     at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717)
    tomcat-cont       |     at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:973)
    tomcat-cont       |     at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1850)
    tomcat-cont       |     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    tomcat-cont       |     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    tomcat-cont       |     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    tomcat-cont       |     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    tomcat-cont       |     at java.lang.Thread.run(Thread.java:748)
    tomcat-cont       |
    tomcat-cont       | 10-Jul-2018 08:20:36.860 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive /usr/local/tomcat/webapps/ROOT.war has finish
    ed in 105 ms
    

    this error happened when I wanted to try restart container when new war is builded.

  • Denis Stephanov
    Denis Stephanov almost 6 years
    Thanks for advice, I'm going to try it. I will try find some maven plugin which after building war copy that war into my specific folder, which will be mapped into /usr/local/tomcat/webapps/ with volume command and I will give you feedback.
  • Denis Stephanov
    Denis Stephanov almost 6 years
    still doesn work :/ I create profile as you said, and there set configuration to custom outpurdirectory which will be always exists. In docker compose I define like that: - D:\workspace\myproj\docker\webapps\:/usr/local/tomcat/webapp‌​s/ generated war has name ROOT.war and after first running application starts. When I run mvn clean package after little change in code, application is not loaded anymore.