In the last four years, we have saw the evolution of Docker, and many videos, posts and events about that. I’m not going to present (another time) the advantages of Docker. If you want to know some of them, you can read them here, and here, and here….
My focus in this post series is how Docker, and mainly Docker Compose can be used by Grails Developers in order to achieve a faster, reproducible and secure development environment. As an IT Analyst, one of the main problems that I had to face in my professional life was WOMM (Works On My Machine).
This kind of problem is very common in the IT world. It occurs because developers configure their own development environment. So they choose their operational system, add things in the database, have their own jars, a specific Java Virtual Machine version and many other things to guarantee that the new feature works! But, the best part, they don’t even know the things they have done in order to “make it work”. And of course, when then they run this feature in another machine, it doesn’t work!
Another problem that occurred sometimes is DIG (Database Is Gone). This occurs when accidentally someone drops the shared database! Or if destroys some important data. Believe me, these are not a good things to do!
So, in order to try to avoid these “events” to happen, I started searching viable solutions. I wanted a way to create the “same” environment to all developers of my team. But I also wanted to enable them to recreate this environment many times if needed, and in a very fast way! So I found this video, from Iván López. He presented an easy way to use Docker, in order to develop Grails applications. I tried to follow his tips, and sooner than latter I was able to reproduce his presentation. I had I Dockerfile, running Grails. Recently, I decided to share it with the world, and create this repository. The source code can be found here.
Then, I used the same Dockerfile to install Postgres, as Iván López suggested. But with the time, our team came to the conclusion that the development environment could not be done in only one Dockerfile, because we had Grails as backend, AngularJS as frontend, Postgres, OpenLDAP, Redis, and other systems…. So, our Dockerfile would have to be VERY BIG in order to install all those systems. Also, if we needed to change Grails version, for instance, we would have to build all the commands in that Dockerfile. Moreover, we had no easy way to follow the logs from all those systems. Definitely it was not viable.
So I decided to “translate” our environment to Docker Compose. But, was Docker Compose the right tool to use in an development environment?
The fist try was something like this:
version: '2' services: grails: image: vaidegrails/grails:latest command: tail -f /dev/null volumes: - ~/.m2:/home/developer/.m2 - ~/.gradle:/home/developer/.gradle - ~/.grails:/home/developer/.grails - ~/.ssh/:/home/developer/.ssh - ~/git/:/home/developer/git ports: - "8888:8080" #Grails default port mapped to my local port 8888 - "5005:5005" #Grails debug port mapped to my local port 5005 links: - db db: image: postgres:9.6 ports: - "7777:5432" #Postgres default port mapped to my local port 5432 environment: - POSTGRES_PASSWORD=vaidegrails
If you don’t know Docker Compose, let me explain what all does this mean. First of all, version: ‘2’ defines the version of docker compose file we are using. In this case, version 2. The most recent version is 3 (and this code is compatible with that).
Then, we have a section services, in which we define 2 services (that will be provided by different containers): grails and db. Grails is the container that we will use to run Grails commands, like:
- grails run-app (or grails r-a)
- grails test-app (or grails t-a)
- grails clean
- grails compile
DB is the database service, in our case, an instance of the Postgres DB, version 9.6.
In Grails service, the container will run the same image of Docker that I created (vaidegrails/grails), in its most recent version (latest). With the block command, we choose which command that will run when the container starts. In this case, we are running tail -f /dev/null. This command runs forever, so our container never stops.
In volumes block, we map directories of our machine to the container. So my local directory ~/.m2 will be mapped in the container’s /home/developer/.m2 directory. It is the Maven local repository in which Grails will download all the projects dependencies. If we don’t map it, each time that we restart the container, and try to run our projects, Grails will have to download all the dependencies again. The same applies to .gradle and .grails directories.
We can map our .ssh directory, for example, to use git with our own ssh public key. The last directory, git, is the directory in my machine that I put all my projects. This way, I can open all my projects in the container. However, you can choose whatever directory you want.
In ports section, we map the containers exposed ports (in the Dockerfile) to our local ports. So, I’ll be to access my Grails running application in my browser entering in http://localhost:8888. You can choose any port you want.
The links section defines that our Grails container is able to access the DB container. How we didn’t define an alias, the DB container can be accessed in the host db. For instance, to connect to Postgres default port (5432), we have to access this host:
This information will be used in our application.yml (we will see how in the next post). The db container, like I said, will use postgres:9.6 image, and will have its 5432 port mapped to our local 7777 port. The enviroment section defines environment variables that will be passed to the image. In our case, we define POSTGRES_PASSWORD variable, with the value “vaidegrails“. This way, our postgres user will be able to access the database with the password “vaidegrails“.
We didn’t define any network for our services, so Docker will automatically create a network and put the two services in the same network. If we need, we can specify different networks for our services, but we won’t do it now.
To run this docker-compose file (the name of the file must be docker-compose.yml), we only need to enter on the directory it is and use the following command:
Or, if we are at another directory, we can specify the full path of the docker-compose.yml file using the parameter -f. With this command, the docker-compose file can be named in a different way:
docker-compose -f full_path_to_the_docker_compose_file up
When you run one of those commands, you will see a log like this:
db_1 | The files belonging to this database system will be owned by user "postgres". db_1 | This user must also own the server process. db_1 | db_1 | The database cluster will be initialized with locale "en_US.utf8". db_1 | The default database encoding has accordingly been set to "UTF8". db_1 | The default text search configuration will be set to "english". db_1 | db_1 | Data page checksums are disabled. db_1 | db_1 | fixing permissions on existing directory /var/lib/postgresql/data ... ok db_1 | creating subdirectories ... ok db_1 | selecting default max_connections ... 100 db_1 | selecting default shared_buffers ... 128MB db_1 | selecting dynamic shared memory implementation ... posix db_1 | creating configuration files ... ok db_1 | running bootstrap script ... ok db_1 | performing post-bootstrap initialization ... ok db_1 | syncing data to disk ... ok db_1 | db_1 | Success. You can now start the database server using: db_1 | db_1 | pg_ctl -D /var/lib/postgresql/data -l logfile start db_1 | db_1 | db_1 | WARNING: enabling "trust" authentication for local connections db_1 | You can change this by editing pg_hba.conf or using the option -A, or db_1 | --auth-local and --auth-host, the next time you run initdb. db_1 | waiting for server to start....LOG: could not bind IPv6 socket: Cannot assign requested address db_1 | HINT: Is another postmaster already running on port 5432? If not, wait a few seconds and retry. db_1 | LOG: database system was shut down at 2017-04-24 11:31:54 UTC db_1 | LOG: MultiXact member wraparound protections are now enabled db_1 | LOG: database system is ready to accept connections db_1 | LOG: autovacuum launcher started db_1 | done db_1 | server started db_1 | ALTER ROLE db_1 | db_1 | db_1 | /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/* db_1 | db_1 | LOG: received fast shutdown request db_1 | LOG: aborting any active transactions db_1 | waiting for server to shut down...LOG: autovacuum launcher shutting down db_1 | .LOG: shutting down db_1 | LOG: database system is shut down db_1 | done db_1 | server stopped db_1 | db_1 | PostgreSQL init process complete; ready for start up. db_1 | db_1 | LOG: database system was shut down at 2017-04-24 11:32:08 UTC db_1 | LOG: MultiXact member wraparound protections are now enabled db_1 | LOG: autovacuum launcher started db_1 | LOG: database system is ready to accept connections
This mean that our service db (with a Postgres instance) is running. We can check our services with this command:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b5db5ab813c1 vaidegrails/grails:latest "tail -f /dev/null" 2 minutes ago Up 2 minutes 0.0.0.0:5005->5005/tcp, 0.0.0.0:8888->8080/tcp bruno_grails_1 09bd98c12dea postgres:9.6 "docker-entrypoint..." 2 minutes ago Up 2 minutes 0.0.0.0:7777->5432/tcp bruno_db_1
We can see the container id, the name of the image that is running in each container, the command that is running, the time it was created, the container status (if is running or not), the ports that are mapped in it and the name assigned to the container. The name is composed by the name of the directory of the docker-compose file (in our case bruno), the name of the service (grails or db), and the number of the instance.
Now we have our development environment running. To stop it, we can use:
docker-compose -f full_path_to_the_docker_compose_file down
It will stop all the services:
Stopping bruno_grails_1 ... done Stopping bruno_db_1 ... done Removing bruno_grails_2 ... done Removing bruno_grails_1 ... done Removing bruno_db_1 ... done Removing network bruno_default
In the next post, we will see how can we configure our Grails application to connect with the database, and how we develop using the Grails container.
“Therefore, it is better for two to be together, than for one to be alone. For they have the advantage of their companionship. If one falls, he shall be supported by the other. Woe to one who is alone. For when he falls, he has no one to lift him up. And if two are sleeping, they warm one another. How can one person alone be warmed? And if a man can prevail against one, two may withstand him, and a threefold cord is broken with difficulty.” Ecclesiastes 4:9-12 https://www.bible.com/42/ECC.4.9-12