Let's start by creating a directory, compose, as a sibling to the users and notes directories. In that directory, create a file named docker-compose.yml:
version: '3'
services:
db-userauth:
image: "mysql/mysql-server:5.7"
container_name: db-userauth
command: [ "mysqld", "--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci",
"--bind-address=0.0.0.0" ]
expose:
- "3306"
networks:
- authnet
volumes:
- db-userauth-data:/var/lib/mysql
- ../authnet/my.cnf:/etc/my.cnf
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "true"
MYSQL_USER: userauth
MYSQL_PASSWORD: userauth
MYSQL_DATABASE: userauth
restart: always
userauth:
build: ../users
container_name: userauth
depends_on:
- db-userauth
networks:
- authnet
- frontnet
restart: always
db-notes:
image: "mysql/mysql-server:5.7"
container_name: db-notes
command: [ "mysqld", "--character-set-server=utf8mb4",
"--collation-server=utf8mb4_unicode_ci",
"--bind-address=0.0.0.0" ]
expose:
- "3306"
networks:
- frontnet
volumes:
- db-notes-data:/var/lib/mysql
- ../frontnet/my.cnf:/etc/my.cnf
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "true"
MYSQL_USER: notes
MYSQL_PASSWORD: notes12345
MYSQL_DATABASE: notes
restart: always
notes:
build: ../notes
container_name: notes
restart: always
depends_on:
- db-notes
networks:
- frontnet
ports:
- "3000:3000"
restart: always
networks:
frontnet:
driver: bridge
authnet:
driver: bridge
volumes:
db-userauth-data:
db-notes-data:
That's the description of the entire Notes deployment. It's at a fairly high level of abstraction, roughly equivalent to the options on the command-line tools we've used so far. Further details are located inside the Dockerfiles, which are referenced from this compose file.
The version line says that this is a version 3 compose file. The version number is inspected by the docker-compose command, so it can correctly interpret its content. The full documentation is worth reading at https://docs.docker.com/compose/compose-file/.
There are three major sections used here: services, volumes, and networks. The services section describes the containers being used, the networks section describes the networks, and the volumes section describes the volumes. The content of each section matches the intent/purpose of the commands we ran earlier. The information we've already dealt with is all here, just rearranged.
There are two database containers, db-userauth and db-notes. Both reference the Dockerhub image using the image tag. For the databases, we did not create a Dockerfile, but instead built directly from the Dockerhub image. The same happens here in the compose file.
For the userauth and notes containers, we created a Dockerfile. The directory containing that file is referenced by the build tag. To build the container, docker-compose looks for a file named Dockerfile in the named directory. There are more options for the build tag, which are discussed in the official documentation.
The container_name attribute is equivalent to the --name attribute and specifies a user-friendly name for the container. We must specify the container name in order to specify the container hostname in order to do Docker-style service discovery.
The command tag overrides the CMD tag in the Dockerfile. We've specified this for the two database containers, so we can instruct MySQL to bind to IP address 0.0.0.0. Even though we didn't create a Dockerfile for the database containers, there is a Dockerfile created by the MySQL maintainers.
The networks attribute lists the networks to which this container must be connected and is exactly equivalent to the --net argument. Even though the docker command doesn't support multiple --net options, we can list multiple networks in the compose file. In this case, the networks are bridge networks. As we did earlier, the networks themselves must be created separately, and in a compose file, that's done in the networks section.
Each of the networks in our system is a bridge network. This fact is described in the compose file.
The expose attribute declares which ports are exposed from the container, and is equivalent to the EXPOSE tag. The exposed ports are not published outside the host machine, however. The ports attribute declares the ports that are to be published. In the ports declaration, we have two port numbers: the first being the published port number and the second being the port number inside the container. This is exactly equivalent to the -p option used earlier.
The notes container has a few environment variables, such as TWITTER_CONSUMER_KEY and TWITTER_CONSUMER_SECRET, that you may prefer to store in this file rather than in the Dockerfile.
The depends_on attribute lets us control the start up order. A container that depends on another will wait to start until the depended-upon container is running.
The volumes attribute describes mappings of a container directory to a host directory. In this case, we've defined two volume names, db-userauth-data and db-notes-data, and then used them for the volume mapping.
To explore the volumes, start with this command:
$ docker volume ls
DRIVER VOLUME NAME
...
local compose_db-notes-data
local compose_db-userauth-data
...
The volume names are the same as in the compose file, but with compose_ tacked on the front.
You can inspect the volume location using the docker command line:
$ docker volume inspect compose_db-notes-data $ docker volume inspect compose_db-userauth-data
If it's preferable, you can specify a pathname in the compose file:
db-auth:
..
volumes:
# - db-userauth-data:/var/lib/mysql
- ../userauth-data:/var/lib/mysql
db-notes:
..
volumes:
# - db-notes-data:/var/lib/mysql
- ../notes-data:/var/lib/mysql
This is the same configuration we made earlier. It uses the userauth-data and notes-data directories for the MySQL data files for their respective database containers.
The environment tag describes the environment variables that will be received by the container. As before, environment variables should be used to inject configuration data.
The restart attribute controls what happens if, or when, the container dies. When a container starts, it runs the program named in the CMD instruction, and when that program exits, the container exits. But what if that program is meant to run forever, shouldn't Docker know it should restart the process? We could use a background process supervisor, such as Supervisord or PM2. But, we can also use the Docker restart option.
The restart attribute can take one of the following four values:
- no – do not restart
- on-failure:count – restart up to N times
- always – always restart
- unless-stopped – start the container unless it was explicitly stopped