Tomcat container - is it possible to create a Docker volume for tomcat/conf directory?

Hi!
I’m playing something with Tomcat. I would like to have conf directory as volume on Docker host. This is my docker-compose.yaml:

version: '3.3'
services:
   tomcat:
     image: tomcat:10.1.15-jdk17
     container_name: tomcat
     ports:
       - '8084:8080'
     volumes:
       - ./tomcat/conf:/usr/local/tomcat/conf
       - ./tomcat/webapps:/usr/local/tomcat/webapps

I also have directories created on host, for both volumes.

[root@archmedia docker-compose-tomcat]# tree
.
├── docker-compose.yaml
└── tomcat
    ├── conf
    └── webapps

However, this wont work, container not starting, and this is everything what I can see from docker logs:

[root@archmedia docker-compose-tomcat]# docker logs tomcat
Nov 04, 2023 8:01:59 AM org.apache.catalina.startup.Catalina parseServerXml
WARNING: Unable to load server configuration from [/usr/local/tomcat/conf/server.xml]
java.io.FileNotFoundException: /usr/local/tomcat/conf/server.xml (No such file or directory)
        at java.base/java.io.FileInputStream.open0(Native Method)
        at java.base/java.io.FileInputStream.open(FileInputStream.java:216)
        at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
        at java.base/java.io.FileInputStream.<init>(FileInputStream.java:111)
        at java.base/sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:86)
        at java.base/sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:189)
        at org.apache.catalina.startup.CatalinaBaseConfigurationSource.getResource(CatalinaBaseConfigurationSource.java:118)
        at org.apache.tomcat.util.file.ConfigurationSource.getConfResource(ConfigurationSource.java:150)
        at org.apache.tomcat.util.file.ConfigurationSource.getServerXml(ConfigurationSource.java:127)
        at org.apache.catalina.startup.CatalinaBaseConfigurationSource.getServerXml(CatalinaBaseConfigurationSource.java:52)
        at org.apache.catalina.startup.Catalina.parseServerXml(Catalina.java:631)
        at org.apache.catalina.startup.Catalina.load(Catalina.java:732)
        at org.apache.catalina.startup.Catalina.load(Catalina.java:769)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at org.apache.catalina.startup.Bootstrap.load(Bootstrap.java:307)
        at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:477)

Nov 04, 2023 8:01:59 AM org.apache.catalina.startup.Catalina start
SEVERE: Cannot start server, server instance is not configured

From my understanding, at least this was the way I done before with all my containers, it is enough to create some empty directory on the host, then in docker-compose.yaml define a volume as below, and all files from that location in container will be shown in host directory:

     volumes:
       - ./host/directory:/container/directory

Also, if I omit - ./tomcat/conf:/usr/local/tomcat/conf in my docker-compose.yaml, and using only - ./tomcat/webapps:/usr/local/tomcat/webapps, everything is working as expected - container starts successfully, it is accessible on IP:PORT and all container files form /usr/local/tomcat/webapps are presented on host inside ./tomcat/webapps. E.g. like this:

version: '3.3'
services:
   tomcat:
     image: tomcat:10.1.15-jdk17
     container_name: tomcat
     ports:
       - '8084:8080'
     volumes:
       - ./tomcat/webapps:/usr/local/tomcat/webapps

Does anybody know why conf directory cannot be mounted as volume?

Thanks

it can and it does. Though, if you bind a host path into a container path, it will be mounted on top of the existing path, thus eclipsing the original content.

If /usr/local/tomcat/conf/server.xml exists in the image, you will need to provide the server.xml in your hosts’s ./tomcat/conf folder when you bind it into the container path /usr/local/tomcat/conf.

Usually people solve this by having a copy of the file in a different location, then check in their entry point script if the file already exists at the target location, and if not copy it there.

I am now little confused. This wasn’t the way how it worked with my previous containers, neither with another volume in this one: - ./tomcat/webapps:/usr/local/tomcat/webapps. I mean, at the very beginning when I created directories on the host, ./tomcat/conf and ./tomcat/webapps, both directories were empty. In these host directories, I didn’t put any equivalent file(s) from container respective directories. However, after first docker-compose up -d, host directory ./tomcat/webapps was populated with files from container location /usr/local/tomcat/webapps. On the other hand, host directory ./tomcat/conf wasn’t populated with any file from container location /usr/local/tomcat/conf, it was stay empty and it is blocking container to start, because it complains that cannot find server.xml file, which I’m expecting should be in container (and it is of course, I can see it when disable this volume). I am not such a Docker expert and I am aware that need to learn and understand more, but I’m already running 18 docker containers in my home (Home Assistant, ESPHome, Hyperion and similar), all of them with docker-compose and all of them are creating persistent volumes in way I described above - create empty directory on the host manually, set - ./host/directory:/container/directory in docker-compose.yaml, and all files from that container location will be presented in host directory.

Your description just confirms what I wrote about: if a bind is used the container can not see the original content of path, as it is eclipsed by the content of the host folder you mount into.

It literately uses the same mechanism as mount --bind /source/path /host/path uses. Test it on your host and you will understand how it works.

Though, if you use named volumes instead (!=host path), there is a mechanism in place that, if the volume is empty (and only then!), copies existing content in the container path back into the volume, before it’s mounted into the container path (and eclipses the original container path). It is a bad practice to really on that mechanism: If you update the image and that path would contain different files, they would never be copied back into your volume + users that use binds instead of named volumes would face the problem you face right now. That’s why I wrote how people usually solve this.

1 Like

I don’t think that my description confirms what you wrote about. I understand you that:

     volumes:
       - ./tomcat/webapps:/usr/local/tomcat/webapps

…will take a content of host folder and overlay it over container folder. This is not the true in my case, because as said, I started with empty ./tomcat/webapps directory on the host, and after docker-compose up -d, I got the content from the container location /usr/local/tomcat/webapps inside host directory ./tomcat/webapps (which was empty before). So my understanding is that this mount is going container → host, not host → container as I’m understanding what you wrote, no?

Please read the two links my last post contains, and please try out how mount --bind actually works.

Never mind, forget and thanks. Btw, I know how mount --bind is working, using it a lot, but thanks for the hint anyway :wink: