Before we start to talk about docker-compose, ensure you have installed Docker and Docker Compose.
If your project/app uses multiple containers then it will be unreasonable trying to run the individual containers separately and then linking them. It is possible but tiresome, brings complexity in scaling and not one of DevOps recommendations. So here comes docker-compose to the rescue. Docker-compose helps you to run multiple containers, linking them and defining various container properties in one file. This file is called docker-compose.yml.
Docker-compose file
This file contains all the defined container properties and their images to be used in a specified project/app. It has changed over the years from version 1 to version 3. So it is a requirement to specify which version you are using at the start of each docker-compose.yml file. Docker-compose files are written in YAML format and are in the format of docker-compose.yml. A typical docker-compose file looks like this:
version: "3"
services:
db:
image: postgres
environment:
POSTGRES_USER: zuri
POSTGRES_DB: zuri
POSTGRES_PASS: zuri1234
volumes:
- pgdata:/var/lib/posgresql/data
zuri:
build:
context: .
ports:
- "8000:8000"
volumes:
- ./zuri:/zuri
command: python manage.py runserver 0.0.0.0:8000
depends_on:
- db
volumes:
pgdata:
The docker-compose file consists of various components. I won’t talk about all of them but I will touch on some important ones. Essentially the compose file defines services that control the containers. From the above example you can see I have two services, db and zuri. The db service is my database container while the zuri service is my project. The two communicate via the property depends_on. Which tells docker-compose to create the service should it not exist when running zuri service.
Under each service you can define many properties like ports to expose, volumes to use, network e.t.c. Of course, each container uses an image and therefore you can directly define the image in the docker-compose file or specify a Dockerfile to be used. In this example, you can see in zuri service I have defined build context with a . this tells docker-compose to look for a docker file in the same directory to build an image for the service. But the database service has the image Postgres directly defined.
Here is the zuri service dockerfile:
#base image
FROM python:3
#maintainer
LABEL Author="CodeGenes"
# The enviroment variable ensures that the python output is set straight
# to the terminal with out buffering it first
ENV PYTHONBUFFERED 1
#directory to store app source code
RUN mkdir /zuri
#switch to /app directory so that everything runs from here
WORKDIR /zuri
#copy the app code to image working directory
COPY ./zuri /zuri
#let pip install required packages
RUN pip install -r requirements.txt
Docker container lifecycle
Docker containers are ephemeral, that is, they are deleted when you delete the container. Their status depends on the status of the applications they host. The various states include:
1. Create container $ docker create –name <container-name> <image-name>
2. Run container $ docker run -it -d –name <container-name> <image-name>
3. Pause container $ docker pause <container-id/name>
4. Unpause container $ docker unpause <container-id/name>
5. Start container $ docker start <container-id/name>
6. Stop container $ docker stop <container-id/name>
7. Restart container $ docker restart <container-id/name>
8. Kill container $docker kill <container-id/name>
9. Remove/delete container $docker rm <container-id/name>
Docker-compose commands
Compose of course being that it manages the containers, it also contains the various container commands to manage the whole container lifecycle. Some of the commands include start, stop, build view e.t.c
To view these commands and what they do, run this on your terminal:
$ docker-compose --help
Define and run multi-container applications with Docker.
Usage:
docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
docker-compose -h|--help
Options:
-f, --file FILE Specify an alternate compose file (default: docker-compose.yml)
-p, --project-name NAME Specify an alternate project name (default: directory name)
--verbose Show more output
--no-ansi Do not print ANSI control characters
-v, --version Print version and exit
-H, --host HOST Daemon socket to connect to
--tls Use TLS; implied by --tlsverify
--tlscacert CA_PATH Trust certs signed only by this CA
--tlscert CLIENT_CERT_PATH Path to TLS certificate file
--tlskey TLS_KEY_PATH Path to TLS key file
--tlsverify Use TLS and verify the remote
--skip-hostname-check Don't check the daemon's hostname against the name specified
in the client certificate (for example if your docker host
is an IP address)
--project-directory PATH Specify an alternate working directory
(default: the path of the Compose file)
Commands:
build Build or rebuild services
bundle Generate a Docker bundle from the Compose file
config Validate and view the Compose file
create Create services
down Stop and remove containers, networks, images, and volumes
events Receive real time events from containers
exec Execute a command in a running container
help Get help on a command
images List images
kill Kill containers
logs View output from containers
pause Pause services
port Print the public port for a port binding
ps List containers
pull Pull service images
push Push service images
restart Restart services
rm Remove stopped containers
run Run a one-off command
scale Set number of containers for a service
start Start services
stop Stop services
top Display the running processes
unpause Unpause services
up Create and start containers
version Show the Docker-Compose version information
Docker-compose run, up, exec – what’s the difference?
Well, when I started to use docker, I would notice that the tutorials often used docker-compose up, docker-compose run and docker-compose exec mostly without explaining. I know you can simply read the difference in the docs of the help command line but sometimes it is helpful to fully appreciate the difference by digging a little deeper.
So which one should you use and at what point? I will illustrate these differences using my Django docker project on GitHub.
Docker Compose Up
This command is used when you want to start or bring up all services in your docker-compose.yml file. Docker-compose.yml file defines your services, their properties, variables, and dependencies.
docker-compose up
To view containers running:
docker-compose ps
You can also tell docker-compose to run only one service e.g.
docker-compose up zuri
Docker Compose Run
This command will spin up a new container for you to use. It is mostly used when you want a new container or the container is not running and is a one-off process which avoids conflicts with other services in your docker-compose.yml
that might be running.
docker-compose run zuri python manage.py migrate
Docker Compose Exec
This command is used when you want to interact with a container that is already running. It, therefore, requires to be run with a command argument. Before you exec the container must be in running state!
$ docker-compose exec zuri bash
That’s it for now. Peace Out!
Also check our previous articles: