Docker Compose
Docker Compose (aka docker-compose) is a simplified method to defining and configuring a whole Docker stack of services, allowing for quick configuration of not just single containers, but multiple containers/services, and their interactivity between each other.
While all functionality of docker-compose can be re-created using only a Dockerfile and manual commands or scripted shell script, a docker-compose file allows quick and simple configuration through a single yaml file per environment (e.g. dev, test, prod).
Below we will go over the sections of docker-compose and how they're used, with a final completed sample at the end.
Offial documentation on Docker Compose can be found on Docker's Docs site.
File Naming
Docker Compose by default, will always look for a docker-compose.yml file in the directory where it's commands are ran; if a standard file is found, Docker Compose will use that for executing given commands.
This functionality enables users the option of either:
-
Define a standard file (
docker-compose.yml) for a single environment and be Docker Compose ready for all commands -
or, Define multiple environments using variations of:
docker-compose.ymlfor developmentdocker-compose.test.yamlfor testingdocker-compose.prod.yamlfor production
By utilizing multiple configuration files for each environment, we are able to define custom settings (e.g. no web server needed during development), relevant variables (e.g. use test dataabse for testing), and services/containers (e.g. running a buidl/migration step for production).
Example: Wanting to setup both, a development and production configuration separately, we will create:
docker-compose.ymlfor developmentdocker-compose.prod.ymlfor production
Version
The version keyword tells docker-compose what version of configurations we will be using. This helps with issues of using configurations, keywords, etc. that may have been used in earlier versions, but have since changed.
The first line of our docker-compose file should always be the version keyword.
Example: If we wanted to use version 3.7 (latest at the time of documentation), we would use:
version: "3.7"
Services
Services with ** require additional configuration variables defined on this page
Certain service parameters like Persistent Volumes and Shared Netowrks require additional configuration outside of the Services section. Services with this identifier will have their own section in this document.
The services section is where we define all the containers that we want Docker to create and their configurations/relations.
Each entry in the services section can have varying options and environment variables, which will be best defined on the core images' Dockerhub page.
Below is a matrix of popular parameters for services and wether they're required for base functionality:
| Parameter | Required | Default Value | Description |
|---|---|---|---|
| container_name | N | random string | Custom name for container; extremely useful to identify containers that belong to a stack. |
| image | Y | Image name to be used for container. This is replaced by build when using Dockerfile to build a custom image. |
|
| build | Y | Path to Dockerfile. This is replaced by image when using pre-built imaged from Dockerhub |
|
| restart | N | never | Define if container should attempt to restart on fail or docker boot (e.g. system restart) |
| environment | N | Environment variables that should be passed into the container. | |
| ports | N | Port mapping from local system to container | |
| volumes | N | Docker Volume and local filesystem mapping into the container. Mapping a local directory to the container (e.g. ./src) will ensure the files are always in-sync, in both directions, even while the container is running. |
|
| networks | N | Shared network mapping between containers.** | |
| command | N | Custom command to be ran on container start/boot.** |
If we wanted to define:
- Node service that builds from our Dockerfile
- MySQL database from a pre-built image in Dockerhub
- Allow containers to communicate between each other
- Ensure our DB data persists even when containers are shutdown/destroyed
- Expose our Node service to our local system on port 80
we would use:
version: "3.7" services: # our node application node_app: container_name: node_web # custom name for the container build: . # use Dockerfile located project root restart: unless-stopped # always restart, unless manually stopped environment: # define environment variables NODE_ENVIRONMENT: dev ports: # map ports from local:container - 80:8000 volumes: # map our source code into container workdir - ./src:/code networks: # use shared network between containers - node_db_network # mysql db for our node application node_db: container_name: node_db # custom name for the container image: mysql:latest # name:version of pre-built image restart: unless-stopped # always restart, unless manually stopped environment: # define environment variables MYSQL_ROOT_PASSWORD: root_pwd MYSQL_DATABASE: app_db MYSQL_USER: db_admin MYSQL_PASSWORD: db_admin_pwd volumes: # map persistent volume to DB store - node_db_volume:/var/lib/mysql networks: # use shared network between containers - node_db_network
Volumes
In docker-compose, volumes can be used to:
- Create simple mappings of local code/config directories into the container,
- Map local directories to sync and retain data from within the container,
- Define Persistent Volumes that are named and kept even when containers are shutdown/recreated.
Simple Volumes
In our examples we use a simple mapping to map our ./src directory into the container /code work directory, which ensures our source code is always in-sync, allowing us to make live edits to code while the containers are running.
volumes: - ./src:/code
Persistent Volumes
For our database however, we do not have pre-existing data that we would want MySQL to use; instead, we would like to persist our data that is created once our application and database are up and running in Docker.
To achieve this, we will map out network similar to a Simple Volume, but add a separate configuration outside of the Services section:
# our usual services definition, where we # map our persistent volume to the container services: node_db: ... volumes: - node_db_network # persistent volume definition volumes: node_db_network:
Networks
While we do define and expose ports on the individual containers to our local system, there are cases where we want certain containers to communicate with each other, and only each other.
To achieve this, we create named networks and then define which containers have access to them.
If we wanted to let our node_web container communicate with the database in node_db container, without exposing it to our local system, we would use:
# our usual services definition, where we map our # named network to the containers that are allowed to use it services: node_web: ... networks: - node_db_network node_db: ... networks: - node_db_network # named network definition networks: node_db_network:
Completed Example
version: "3.7" services: # our node application node_app: container_name: node_web # custom name for the container build: . # use Dockerfile located project root restart: unless-stopped # always restart, unless manually stopped environment: # define environment variables NODE_ENVIRONMENT: dev ports: # map ports from local:container - 80:8000 volumes: # map our source code into container workdir - ./src:/code networks: # use shared network between containers - node_db_network # mysql db for our node application node_db: container_name: node_db # custom name for the container image: mysql:latest # name:version of pre-built image restart: unless-stopped # always restart, unless manually stopped environment: # define environment variables MYSQL_ROOT_PASSWORD: root_pwd MYSQL_DATABASE: app_db MYSQL_USER: db_admin MYSQL_PASSWORD: db_admin_pwd volumes: # map persistent volume to DB store - node_db_volume:/var/lib/mysql networks: # use shared network between containers - node_db_network # persistent volume definition volumes: node_db_network: # named network definition networks: node_db_network: