Blog

Gavin Pickin

September 07, 2017

Spread the word


Share your thoughts

First Docker Compose File - part 2

The Ortus Solution roadshow continues, we'll keep bringing you free webinars and blog posts through the month of September. If you have tuned in already, you might have learned what Docker is, why you could / should use it, and then maybe a little of how to use it. If the first part of this mini series on First Docker Compose we saw how you can spin up a Docker container pretty easily, with a command or two, but usually, we work with multiple servers. Docker-Compose makes that easier, it doesn't have to be too confusing. We are building on top of part 1, where we spun up a CFML Server container by itself, and then we added a simple MySQL Server. Next we're going to add Nginx in front of the CFML Server container, and then we'll do more with the MySQL server, like add a database, and then seed it ( preload it ) with data to get things rolling. That is what we're going to look at today.

If you want to see 1 & 2, click here for Part 1

3 - Nginx in front of CFML Container with MySQL Server

Most people run a web server in front of their CFML setup, and nginx becoming more and more popular all the time, we'll use that in our setup.

Nginx is a little trickier due to the configuration, but thanks to volumes and a dockerfile command to copy configs, we can setup all our configuration in our repo. Let's look at our new Docker Compose file.

Docker-Compose.yml

version: "3"

services:
  # CFML Engine
  cfml:
    image: ortussolutions/commandbox  
    # bind public port to 8080
    ports:
      - "8080:8080"
    volumes:
      - ./www:/app
      
  # MySQL Server
  mysql:
    image: mysql
    environment:
      MYSQL_ROOT_PASSWORD: "myp@ssword"
      
  # NGINX Container
  nginx:
     build: ./build/nginx
     ports: 
      - "80:80"
      - "443:443"
     # Mount our shared webroot volume
     volumes: 
      - ./www:/opt/sites/default
      - ./build/nginx/config:/etc/nginx
  • Nginx - this is the name of our service. We're keeping the names simple, but you can name the services after pokemon, or hobbits, spaceships, smurfs, its really up to you.
    • build - Instead of using an image, in this case, we're using a dockerfile. This gives us a little more flexibility, we'll look at the docker file shortly.
    • ports - Again, we want to tell Docker what ports we want to be opened externally to the local machine. This is great for security, because Docker only opens what you tell it to.
      • 80:80 - Local host port 80 maps to internal docker port 80
      • 443:443 - Local host port 443 maps to internal docker port 443
    • volumes - this is where you map code on your machine, into the container.
      • ./www:/opt/sites/default - In this case we're mapping the webroot of ./www into the /opt/sites/default folder in the docker container. Our configuration is setup to serve that folder. This configuration comes from the nginx config files we'll show you soon.
      • ./build/nginx/config:/etc/nginx - We are mapping the nginx config files from the build/nginx/config into the /etc/nginx folder inside the docker container. This allows us to modify the files locally and then Nginx can pick up those changes for a reload or restart and apply them.

Although Docker allows us to pass a lot of information into containers, having to setup virtual hosts, with SSL certs etc through Docker-Compose would be troublesome. So in this case, we wanted to just copy an nginx config folder in. The volume hows keep those files updated, but we still need to initially copy them in, so we will use the docker file.

Folder Structure

First, let's look at the folder structure.

/
/build/
/build/nginx/
/build/nginx/dockerfile
/build/nginx/config/
/build/ngxin/config/*
/www/
/www/Application.cfc
/www/index.cfm
docker-compose.yaml

DockerFile for Nginx

The Docker-compose file points at the /build/nginx/dockerFile for nginx, lets see what that file does.

FROM nginx:alpine

RUN apk add --no-cache bash gawk sed grep bc coreutils

COPY config/ /etc/nginx/
RUN ls -la /etc/nginx/*

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

FROM nginx:alpine - This is just like image in Docker Compose, it tells Docker whats the base image is. Everything is built on top of this image

RUN apk add --no-cache bash gawk sed grep bc coreutils - This installs a few utilities we might want or need.

COPY config/ /etc/nginx/ - This copies the config folder contents into the nginx folder in the container, so all of our Nginx config is in place

RUN ls -la /etc/nginx/* - I believe this is just for debugging, not sure to be honest.

EXPOSE 80 - Tells docker to expose port 80 - which Docker-Compose does as well, so this isn't vital.

CMD ["nginx", "-g", "daemon off;"] - Starts nginx in the foreground by disabling daemon mode.

CFML Files

We did not touch the Application.cfc or the index.cfm in this case.

Start the Container

We spin up this container from the command line with the following command from the root of our project

docker-compose up

When the command has completed, when you visit http://127.0.0.1:8080/ you will see a dump of the user table. This is bypassing the nginx server, and hitting the cfml server directly.

When you visit http://127.0.0.1/ you will see a dump of the user table. This is hitting Nginx directly, and proxying through to the cfml server.

All of the networking is by default, using the service name. You could specify a network, use links ( aliases ) and much more, but with only a few lines of code in the docker-compose.yml, and a config folder, we have nginx in front of cfml, and mysql, all working together.

Create MySQL and preload Data

In scenario 2, we added a MySQL server container, but it didn't have a database, or any data, so everything would still be manual. In this scenario, we want to be able to add a new database, preload it with a table, and some records, and then dump that on our page.

Docker-Compose.yml - MySQL only section

# MySQL Server
  mysql:
    image: mysql
    environment:
      MYSQL_ROOT_PASSWORD: "myp@ssword"
      MYSQL_DATABASE: "test_db"
    restart: always
    ports: 
      - "33066:3306"    
    volumes:
      - ./build/mysql/initdb:/docker-entrypoint-initdb.d

What changed?

MYSQL_DATABASE: "test_db" - We added a database, set the name, and this will be created.

restart: always - This just tells MySQL Container we want it to always restart, we can't function without our DB. This is a handy feature, for all docker containers

ports: - "33066:3306" - This gives us access from our local machine to the docker container to use a MySQL tool.

volumes: - ./build/mysql/initdb:/docker-entrypoint-initdb.d This is a special mysql image setting. Any folder you map to docker-entrypoint-initdb.d will have all the sql scripts in that folder run when spinning up the MySQL server container for the first time.

Folder Structure

/
/build/
/build/mysql/initdb/
/build/mysql/initdb/myfile.sql
/build/nginx/
/build/nginx/dockerfile
/build/nginx/config/
/build/ngxin/config/*
/www/
/www/Application.cfc
/www/index.cfm
docker-compose.yaml

CFML Files

To dump data out of the new table, in the new db, we need to change a couple of small things.

Application.cfc

We change the connection string from the mysql db, to the test_db database

component {
	this.datasources["dsmysql"] = {
		  class: 'org.gjt.mm.mysql.Driver'
		, connectionString: 'jdbc:mysql: //mysql:3306/test_db?useUnicode=true&characterEncoding=UTF-8&useLegacyDatetimeCode=true'
		, username: 'root'
		, password: "encrypted:744d34f22944e1ca2e92bbf13f2617f7fc4c7daa255e440e9c52d856af118228"
	};
}

index.cfm

We need to adjust the query to dump out the new table contents


select * from testtable



Start the Container

We spin up this container from the command line with the following command from the root of our project

docker-compose up

Now when we look at our MySQL tool connecting on port 33066 we see a new DB has been created. The testtable is there, and the data too.

When you visit http://127.0.0.1/ you will see a dump of the testtable table from the new database. This is hitting Nginx directly, and proxying through to the cfml server. You can still access the cfml server directly on 8080.

Persisting Data

If you add these lines to an existing docker container that is up and built, you might need to use a docker-compose down to destroy the container, and then docker-compose up to build it again. Remember, any changes inside the container is lost when you do a down command. If you want to keep your database, you should mount the data with the following config.

- ./build/mysql/mysql_data:/var/lib/mysql

This maps your local folder inside of the docker container, and then when you destroy the container, the data will persist on your machine.

Non persistent data is perfect for spinning up a container with test data, and running tests, and then spinning down, knowing the data will always be the same, and any changes are not needed. Remember persistence, it can bite you if you don't.

Clone the repo and try it out

Clone the repo, and pick your flavor, and spin up your own dev environment today.

https://github.com/Ortus-Solutions/firstDockerCompose

Docker Compose commands

In the examples, we're spinning up a new docker container, so we're always using the up command. You shouldn't use the up command all the time. Its quick, but not as quick as a start command. Below are some basics on the docker compose commands with a link to more information.

docker-compose up - Create and start containers

docker-compose start - Starts an existing UP container, keeping the data as it was in the container.

docker-compose stop - Equivalent to a CTRL-C if you are in CLI mode - stops the services.

docker-compose down - Stop and remove containers, networks, images, and volumes

Read the Docker Compose docs here for more information on command options

There are a lot more options, tune into the roadshow blogs and webinars to learn more.

Add Your Comment

(2)

Jan 10, 2018 14:06:24 UTC

by Blair

How did you figure out the database connection information that was added to the Application.cfc? Esp how the password was encrypted?

Jan 10, 2018 14:10:50 UTC

by Brad Wood

Hi Blair, In Lucee, the administrator will show you exactly what to paste into the Application.cfc to create a datasource. You can get to it by editing an existing datasource or by using the "export" menu option. If you removed the "encrypted:" bit you can also use a plain text password.

Recent Entries

Why BoxLang When You Have Kotlin, Groovy, Scala, and more…

Why BoxLang When You Have Kotlin, Groovy, Scala, and more…

As we approach a stable release of BoxLang and our continued marketing reaches more folks, many have asked about its purpose. Why create a new language when the JVM ecosystem already includes established languages like Kotlin, Groovy, and Scala, to name a few.

Luis Majano
Luis Majano
December 18, 2024
ColdBox Free Tip 6 - Using Routing with Wildcard Domains!

ColdBox Free Tip 6 - Using Routing with Wildcard Domains!

ColdBox gives you the flexibility to create domain-specific routes, making it perfect for multi-tenant applications or projects that need to respond differently based on the domain or subdomain being accessed. In this tip, we’ll dive into how to use the withDomain() method to create routes that match specific domains or sub-domains.

Maria Jose Herrera
Maria Jose Herrera
December 18, 2024