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.