Deploying grails application war to tomcat with docker and docker compose

Recently i have started using Docker for almost all of my projects. It makes it easy to automate the repetative task of installing and configuring the required softwares. I can create a docker setup with a docker-compose file and commit to version control and can use it to reproduce the environment on any machine where docker is running.

This helps me provision the new server with confidence without worrying about missing some required configuration etc. Every thing is in version control and I just need to run a single command, docker-compose up to start application along with dependencies such as a mysql database.

In this blog post, I'll show how do I deploy a war file to tomcat official image.

The goal

  1. Automate the process and be able to reproduce the environment quickly.
  2. Install and configure mysql and tomcat images
  3. Ability to externalize the grails configuration.

I put all docker related configuration inside docker subdirectory of application root. configuration files for docker containers are kept inside docker/conf/<container>

Here is my docker-compose configuration.

File: <app>/docker/docker-compose.yml

version: '2'
services:

  db:
    image: mysql:5.7.10
    container_name: grails_mysql
    #ports:
      #- "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=prod_db
    volumes:
      - "/var/grails/data/mysql:/var/lib/mysql"
      - "./conf/mysql:/etc/mysql/conf.d"


  web:
    image: tomcat:8.5-alpine
    container_name: grails_tomcat
    volumes:
      - ./conf/prod:/etc/conf/grails
      - ./conf/tomcat/setenv.sh:/usr/local/tomcat/bin/setenv.sh
      - ./webapps:/usr/local/tomcat/webapps
      - ./logs:/usr/local/tomcat/logs
    depends_on:
     - db
    ports:
     - "80:8080"

Mysql configuration

The docker/conf/mysql directory is mounted to /etc/mysql/conf.d directory. So now we can put a my.cnf file inside that directory and it will be picked up by mysql.

File: <app>/docker/conf/mysql/my.cnf

[mysqld]
max_allowed_packet=32M

I can put whatever mysql configuration I need in above my.cnf file and it will be used by mysql.

/var/grails/data/mysql directory from host machine is mounted to /var/lib/mysql directory inside mysql container. So I can backup this directory to backup the whole mysql data directory.

Tomcat configuration

docker/webapps is mounted to /usr/local/tomcat/webapps directory inside tomcat container. So now any war file kept inside the webapps directory will get deployed to tomcat.

Externalize grails configuration

I use grails3 external-config grails plugin for externalizing the grails configuration.

grails.config.locations = ["classpath:prod-config.groovy",]

The prod-config.groovy file needs to be made available in classpath so that it can be picked up by application when deployed to tomcat.

 The external configuration file is kept inside docker/conf/prod directory and the directory is mounted to /etc/conf/grails directory inside tomcat container.

Now we need to put the etc/conf/grails directory to tomcat classpath, this is done by creating a setenv.sh file and mounting it to /usr/local/tomcat/bin/setenv.sh.

File: docker/conf/tomcat/setenv.sh (This file should be executable - chmod +x)

CLASSPATH=/etc/conf/grails

Datasource configuration

External configuration file mentioned above can be used to configure datasource for production environment.

File: prod-config.groovy

environments {
	production {
		dataSource {
			 username = "root"
			 password = "root"
			 url = "jdbc:mysql://db:3306/prod_db"
		}		
	}
}

Accessing log files

docker/logs directory is mounted to /usr/local/tomcat/logs directory of tomcat container. Also logback file appender is configured to create the log files under tomcat logs directory. This makes all log files directly accessible from host machine without requiring to go through the tomcat container.

File: grails-app/conf/logback.groovy

String logsDir = System.getProperty('catalina.base', '.') + "/logs"
String logFileName = logsDir + "/appname.log"

 appender("file", RollingFileAppender) {
        file = logFileName
        append = true
        encoder(PatternLayoutEncoder) {
            pattern = "%date [%level] %logger - %msg%n%exception{10}"
        }

        rollingPolicy(TimeBasedRollingPolicy) {
            fileNamePattern = "${logFileName}.%d{yyyy-MM-dd}"
            maxHistory = 15
        }
    }

Thats it, now i have automated the whole process. The whole configuration is checked in to git. Whenever I need to setup an environment on a machine, I just have to checkout the project from git, create a war file and put the war inside docker/webapps directory and run docker-compose up command. It will automatically download tomcat and mysql images if needed, configure it and will create the containers, and the war will be deployed to tomcat.

Look at how to serve static assets with nginx If you want to use nginx as reverse proxy for tomcat and serve static file by nginx.