[Docker] Let's Learn About Docker Compose
Brothers, am I the only one uncomfortable?
This is the second brothers. There are actually many uncomfortable things.
Use of Docker
When did they say we use Docker?
It would be when you want to use the core technology of Docker, Container.
In other words, for reuse.
The Dockerfile
we learned earlier automated many parts required to create a container.
However, there are also disadvantages to Dockerfile
.
As discussed in the previous post, there were limitations in automating the settings of the Host OS.
For example, it couldn’t reach port forwarding of the Host OS or mounting the path of the volume.
Eventually, many developers would create a Dockerfile
, connect containers created through build
, and mount volume
.
Docker Compose
Docker Compose solves the above issues.
The details that can be defined in docker-compose.yml
include volume
, port
, env
as well as support for performing various operations to define containers.
It also not only defines a single container, but also supports the definition of multiple containers and setting relationships between containers.
The official Docker documentation introduces Compose as a container-based application that is platform-agnostic.
.yml
, .yaml
Before learning about Docker Compose, there is something to know.
Earlier, it was stated that docker-compose.yml
can be used to set commands for multiple containers.
So, what is a .yml
file?
Files with .yml
, .yaml
extensions are a form of markup language called Yet Another Markup Language.
It forms a hierarchy through colons and indentation like Python, allowing easy writing of specific objects, configuration values, etc.
To intuitively understand, let’s look at the example below.
car:
name: "bus"
color: "red"
door: 4
capability:
max_weight: "4T"
human: 10
customers:
- name: "Alice"
age: 14
- name: "Ban"
age: 16
- name: "Yang"
age: 26
This form of .yaml
can also be expressed in the same JSON
format as below.
{
"car": {
"name": "bus",
"color": "red",
"door": 4,
"capability": {
"max_weight": "4T"
}
}
}
In this way, .yml
is a file format that is convenient to use when expressing data in a hierarchical form, using existing formats such as JSON
and XML
.
docker-compose.yml
Now let’s create docker-compose.yml
and specify the containers we will use.
services
First, the specification for each container we define is defined as a unit called service
.
If you explain using the .yml
format, it would be this format.
services:
container_1:
...
container_2:
...
image
This is the part where the image to be retrieved is specified.
There are various ways to retrieve the desired image.
# Image name
image: redis
# Image name: tag name
image: redis:5
# Image name@sha256: IMAGE ID
image: redis@sha256:0ed5d5928d4737458944eb604cc8509e245c3e19d02ad83935398bc4b991aac7
# Repository name/image name
image: library/redis
# Registry name/repository name/image name
image: docker.io/library/redis
# Private registry address:port/image name
image: my_private.registry:5000/redis
build
This is used when you want to create a container through Dockerfile
.
services:
frontend:
image: awesome/webapp
build: ./webapp # Acts as context if sub-levels are not specified
backend:
image: awesome/database
build:
context: backend # Path to search for Dockerfile
dockerfile: ../backend.Dockerfile # Specify the file name if it is not Dockerfile
custom:
build: ~/custom # "~" can also be used to specify the home directory
- The
awesome/webapp
image is built using the./webapp
subdirectory in the same folder as the Compose file as the docker build context. An error occurs if there is noDockerfile
in this folder. - The
awesome/database
image is built using the./backend
subdirectory in the same folder as the Compose file. Thebackend.Dockerfile
file is used to define the build stage, and this file (backend.Dockerfile
) is defined based on the context path (./backend
). In this sample, it is interpreted as the parent folder of the Compose file, so the siblingDockerfile
ofbackend.Dockerfile
is also a search target. - The
custom
service is built usingcustom
directory in the user’sHOME
directory as thecontext
.
When image
and build
are both present
It was said that image
creates a container based on the specified image, and build
creates a container using Dockerfile
.
However, since it is possible to retrieve an image through the FROM
keyword in Dockerfile
, one might worry whether these properties might conflict.
You can also find a section on this in the official Docker documentation, confirming our worry that there is no guarantee of which image will be used.
Unless indicated otherwise by the user, it states that it first retrieves the image defined in image
and builds the image from the Dockerfile
if an image cannot be found in the registry.
depends-on
When defining multiple services in docker-compose.yml
, if a certain service needs to start after a particular service has started, you can specify it with depends_on: service name
.
services:
seviceA:
...
depends_on: serviceB
serviceB:
...
entrypoint
Performs the same role as ENTRYPOINT
discussed in the Dockerfile.
In other words, it defines the initial command to execute when the Docker container runs.
As covered in the previous chapter, since this command can be declared only once, if
ENTRYPOINT
is also specified in the Dockerfile through thebuild
option, one (or both) must be deleted.
In many cases, it seems that it is used to define which shell to use initially.
entrypoint: /code/entrypoint.sh
labels
As covered in the LABEL
section of the Dockerfile, it’s an item that allows adding metadata to the container.
environment
It’s used when defining environment variables to be used in the container.
Two methods can be used.
- Map method
environment:
RACK_ENV: development
SHOW: "true"
USER_INPUT: "hello"
- Array method
environment:
- RACK_ENV=development
- SHOW=true
- USER_INPUT=hello
env_file
If you want to define environment variables in a file rather than direct definition, use this option.
env_file: some_env.env
# some_env.env
RACK_ENV=development
VAR="quoted"
When using this method, you can manage environment variables in the form of a file, which has the advantage of easier management.
command
Like we learned in the CMD
section of the Dockerfile
, it’s the part where we define commands to give to the container responsible for the service.
command: ls -al
Since I’m a backend developer, I’ll describe the difference between the two commands (Dockerfile
:CMD
, docker-compose.yml
:command
) from a backend developer’s perspective.
CMD
of the Dockerfile
is generally used for commands to install basic components to configure the server such as pip install
, gradle build
.
Think of it as making it in a form that can run the container (but it’s not mandatory to follow this strictly).
command
of docker-compose.yml
is often used for commands that deploy servers (e.g. ... run serve
) as in commands that run the service.
volumes
volumes
is used to define connecting the user’s created Docker Volume or Host Path and Container Path, or declaring a volume itself.
Specification of volume
The method to define the sub-elements of volumes
combines two types of notation: shorthand and normal syntax.
Generally, shorthand is used but the standard syntax can also be used depending on the circumstances or user’s preference.
- Normal syntax elements
Element | Description |
---|---|
type | Specifies whether to use Docker Volume (volume ) or to bind (bind ) with a Host OS path. |
source | Specify the Volume name or ID, or the Path of the Host OS to be bound. |
target | Enter the Path of the Container. |
read_only | Set if you want to specify it as read-only. |
...
volumes:
- type: volume
source: db-data
target: /data
...
- Shorthand notation
Designate using the format VOLUME:CONTAINER_PATH:ACCESS_MODE
.
ACCESS_MODE
is set torw
by default.
services:
service_1:
image: some/image
# Connecting Docker Volume and Container Path
volumes:
- db-data:/etc/data
service_2:
image: some/image
# Connecting Host OS's Path and Container Path
volumes:
- .:/var/lib/backup/data
service_3:
image: some/image
# Connecting Host OS's Path and Container Path and setting Container Path to read-only
volumes:
- .:/var/lib/backup/data:ro
Defining Volume
If you want to define a Volume through volumes
, place volumes
as the top-level element of docker-compose.yml
.
# Define Docker Volume
volumes:
db-data:
ports
Operates in the same way as the -p
option used in run
and create
of the Docker CLI.
It maps the Port of Host OS and Port of Container Path.
...
ports:
- "8000:8000"
...
In the configuration above, all requests coming to port 8000
of the Host OS will instead be forwarded to port 8000
of the Container.
Docker Compose CLI
Now all that’s left is to create the container.
Using the docker-compose up
command will find and automatically execute the docker-compose.yml
file in the specified directory.
In the newly released Docker Compose V2, it specifies to use the docker compose
keyword to execute, so keep this in mind if using V2.
The -d
option enables the service to run in background mode. (Commonly used)
The -f [COMPOSE_FILE_NAME]
option allows you to run Docker Compose through a specific file other than docker-compose.yml
.
The docker-compose start [SERVICE_NAME]
command allows you to run a specific service within the services.
Conversely, the docker-compose stop [SERVICE_NAME]
command is used to stop a specific service.