docker-compose.ymlfile in the root of the repository, or view it online:
docker-compose uplooks for a
docker-compose.ymlfile in the current directory and translates the instructions in that file to a series of
docker runCLI commands (behind-the-scenes; you won't see this part). The
stdoutoutput of each container will go to your console window unless you add the option
--detach(ed); that output will still be available to you in Docker's logs but you'll get your console back right away.
#in a compose file, so we can comment on the same line as our directives.
docker-compose.ymlthat tracks which directives are included and deprecated over time. Which versions of docker-compose.yml map to which versions of the Docker engine are available in the official document reference, but unless you know you have to support an older version of Docker, use a version corresponding to a recent
stableDocker CE release.
docker volume, a virtual, persistent storage device managed by the Docker daemon. We can remove our container entirely and it will persist unless we explicitly delete the volume using
docker volume delete.
volumesdirective that specifies the source and target mount point for each volume it requires.
networksdirective is to create a "project" network that applies to any service created in the same directory. This would work just fine, but it's best to name your networks so that you can assign each container to one or more of them explicitly. Docker networking is fairly complicated under the hood, but for our purposes, we just need to know which containers need to be able to access which other containers (and so should share a network). Our simple solution here places all of our containers on one network so that each container can access any other container by its name; we'll take advantage of this when configuring our nginx proxy and our CF datasources.
networksdirective just names the network we'll be referring to later on.
networksdirective, the top-level
secretsdirective defines a "Docker Secret" that we'll refer to in each of our services. We'll cover this in more detail below; for now, it's sufficient to know that we want to define individual pieces of data or files that we will mount into one or more containers, and that they're called secrets because this is a mechanism that is used to great effect for storing credentials and any other sensitive information we don't want accessible to anything outside our container.
[registry address]/<image name>:[tag]
:latesttag. This can have unintended consequences -- you might upgrade your database or your web server without realizing that you've done so.
:latestfor MySQL is currently version 8, but we'll get MySQL 5.7 because we've specified that tag. The README for a Docker image will usually document available tags (like the official Docker Hub page for MySQL does/), and a full list is often available under the tags link on that page.
docker container ls, we want to be able to refer to our containers quickly and easily, whether we're outputting logs (
docker logs cfswarm-mysql) or logging in to the container (
docker exec -it cfswarm-mysql bash).
container_namedirective when you have a single container in your service. If we were replicating our service -- running multiple, identical containers in parallel --
container_namewould be forbidden.
docker-compose.yml. These particular variables set the root password, create a database called
cfswarm-simple-dev(if it doesn't already exist), routes the log to the console, and allows connections from any host.
volumedirective under a service, you'll know to look for a top-level volume configuration reference. Because volumes can be shared between services, only the service-specific information goes in the service-level definition.
portsdirective maps one or more ports on the service container to the specified port on the host. In this example, we're mapping MySQL's port 3306 to the same port on our host. Without this directive, our other containers could access MySQL since they all share a
network, but we wouldn't be able to connect to MySQL using any tools on our host unless we ran those tools in Docker as well.
networksdirective refers back to the top-level
networkname we defined earlier. Every container that needs to access that network should have a service-level directive referring to it.
mysqlservice, but with a few alternatives and additions.
volume, but instead of letting Docker create and manage the volume, we're "binding" a directory on the host into the container. This is generally not suitable for production, but if we're actively developing an app then Docker isn't much use to us if we can't read and write to our app folder while it's running.
docker compose.yml. The effect is the same -- the container created for your service doesn't care whether you put your environment variables in your compose file or somewhere else, but the best practice is to keep them separate.
secretsdirective refers back to the secret we defined at the top level. But what is a secret, and why are we using it?
docker configsare not (yet) supported by
docker compose-- they're meant for
docker stackdeployments, which we'll use in production.
docker secretsare for files containing sensitive information: credentials, encryption keys, anything that you don't want to put somewhere easily accessible (such as an environment variable or a layer of your Docker image). These are meant to be supplied to Docker once and then referenced in deployments, rather than read from a file.
configis really what we need, but since it's not supported in
docker compose, we'll use a
secretinstead. The syntax is nearly identical; much like
networks, secrets can be shared between services, so this service-level definition only supplies the
source(the name of the secret in the top-level
secretsconfig) and the
target(name of the file to mount into the
/run/secretsfolder in our container).
docker composethat this service requires other services to be running before it can start. Since our CF engine isn't much use to us without NGINX and MySQL,
docker composewill start these services prior to starting
cfswarm-cfml. Note that Docker doesn't check to see if the services are actually ready -- health checks are more involved and beyond the scope of our development stack. Even so, we want Docker to bring up MySQL and NGINX before starting the CF container.
Dockerfilethat governs how the image is built. Our CF containers are running a script that triggers Commandbox's
server start. NGINX's Dockerfile starts the container with the
nginxcommand, but for development we'll run NGINX in DEBUG mode.
CMDdirective from the Dockerfile with the
nginx-debug -g daemon off.