In this blog post, we’ll provide an introductory example for the syntax of docker-compose.yml.
This describes a Docker setup that consists of two services: a database server (MariaDB in the current version) and the WordPress content management system (CMS).
Most of the settings for the two services should be readily understandable:
- Named volumes are used to store the database files and HTML files, respectively. (The names of the volumes must be specified twice, once in the description of the respective services and a second time—usually at the end of the docker-compose.yml file—in a separate volumes )
- In MariaDB, the wp database and the associated user wpuser are to be set up with the password secret. A random string can be used as root password because it’s not relevant for the setup.
- For WordPress to access the database, the corresponding WORDPRESS variables must be initialized. DB_HOST references the service name db and the default port number of the MariaDB server.
- The web server responsible for WordPress uses port 80 in the container. This port should be connected to port 8082 of the host computer.
# file test/docker-compose.yml
version: '3'
services:
db:
image: mariadb:latest
volumes:
- vol-db:/var/lib/mysql
environment:
MYSQL_RANDOM_ROOT_PASSWORD: 1
MYSQL_DATABASE: wp
MYSQL_USER: wpuser
MYSQL_PASSWORD: secret
restart: always
wordpress:
image: wordpress:latest
volumes:
- vol-www:/var/www/html/wp-content
ports:
- "8082:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_NAME: wp
WORDPRESS_DB_PASSWORD: secret
restart: always
volumes:
vol-www:
vol-db:
“docker compose”
To try out docker compose, you must first go to the directory containing the file dockercompose. yml—here, the wordpress-example directory. Note that the directory name is used to name the containers, volumes, networks, and so on.
docker compose up -d then searches for the docker-compose.yml file in the current directory, downloads the required images (only if they aren’t already available, of course), creates the containers, connects them in a specifically created network, and starts them as background processes (option -d). The entire process takes only a few seconds (plus the time to download the images, if necessary). Note that because of restart: always, the containers are restarted automatically even after a reboot of the computer.
cd wordpress-example (directory with docker-compose.yml)
docker compose up -d
Creating network wordpress-example_default" with the default
driver
Creating wordpress-example_wordpress_1 ... done
Creating wordpress-example_db_1 ... done
The docker ps command displays the two running containers (the output is heavily abridged due to space limitations). After a few seconds required to initialize the container, you can start using WordPress at the address, localhost:8082.
docker ps
ID PORTS NAMES
5211... 0.0.0.0:8082->80/tcp wordpress-example_wordpress_1
d9dc... 3306/tcp wordpress-example_db_1
To stop the execution of all related containers, you need to run docker compose stop. Similarly, docker compose start continues the execution. These commands each require docker-compose.yml to be in the current directory.
You can use docker compose down to stop and delete all the containers of the setups, while the volumes will remain intact. If necessary, you can redeploy the containers and keep the previous data using docker compose up:
docker compose down (stop and delete containers)
...
docker compose up -d (set up new containers)
If you want to delete the containers and their associated volumes, you should run the following commands for this example:
docker compose down
docker volume rm wordpress-example_vol-db \
wordpress-example_vol-www
“docker stack deploy”
Instead of using docker compose, you can also process the docker-compose.yml file via the docker stack deploy command. docker stack requires that the computer is a member of a Docker swarm. However, you absolutely don’t need to create a Docker cluster to use docker stack. It’s perfectly sufficient to run docker swarm init once on your machine. This way, your computer creates a swarm that consists of only one member.
docker swarm init
Swarm initialized: current node (eqbx...) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-xxx 10.0.0.1:2377
To add a manager to this swarm, run
'docker swarm join-token manager' and follow the instructions.
To deploy the containers described in docker-compose.yml along with a network, you must pass the file name of the compose file to docker stack deploy with the -c option and the name of the setup or stack as another parameter. This name will be prepended to the network, volume, and service names. (docker compose uses the name of the directory where docker-compose.yml is located instead of this name by default.)
docker stack deploy -c docker-compose.yml stacktest
Creating network stacktest_default
Creating service stacktest_wordpress
Creating service stacktest_db
docker stack ls proves that the new stack has been set up and consists of two services:
docker stack ls
NAME SERVICES ORCHESTRATOR
stacktest 2 Swarm
You can now use docker service ls to verify that the services have actually been created and are up and running. As before, the output had to be shortened by a few columns:
docker service ls
ID NAME REPLICAS PORTS ...
skz4ab9p71va stacktest_db 1/1
ghdrn0z2gi5n stacktest_wordpress 1/1 *:8082->80/tcp
The REPLICAS column is decisive here. If it reads 0/1, an error occurred during startup. Information about the cause of the error can be obtained from the ERROR column of the output of docker service ps <sid/stackname>, where you pass the service ID or stack name as a parameter. The command specifies which tasks are executed in a service. The ERROR column is usually truncated to the point of being unrevealing. Then, the --notruncoption can provide help:
docker service ps sometest --no-trunc
... NAME ... ERROR
stacktest_db.1 starting container failed: Invalid
address 10.0.1.14: It does not belong
to any of this network's subnets
With docker ps, you can verify that a corresponding container has been created and launched for each service.
Unlike containers, there’s no way to stop and later restart stacks. However, you can delete the entire stack with all its services and networks using docker stack rm:
docker stack rm stacktest
Removing service stacktest_db
Removing service stacktest_wordpress
Removing network stacktest_default
The volumes specified in docker-compose.yml are preserved in this process. For this reason, you can set up and rerun the stack later without losing any data:
docker stack deploy -c docker-compose.yml stacktest
When you no longer need the setup, you can delete both the stack and the mapped volumes:
docker stack rm stacktest
docker volume rm stacktest_vol-db stacktest_vol-www
Debugging
Ideally, when the containers are started by docker compose up, everything goes well. But what happens if errors occur? A first means of troubleshooting is to run docker compose up without the -d option. This way, all logging output of all containers occurs directly in the console. So, to a certain extent, you can follow what’s going on in the containers.
Alternatively, you can use docker ps to take a look at the container list. There, you can usually tell immediately if there are containers that aren’t running as planned or that are constantly being restarted. You can then use docker logs <cname> to read the logging output of the affected containers. docker compose logs shows the combined logging of all containers in the group.
As usual, you can use docker exec to start an additional interactive shell process to look inside a running container:
docker exec -it test_wordpress_1 /bin/sh
ps -ax
PID TTY STAT TIME COMMAND
1 ? Ss 0:00 apache2 -DFOREGROUND
66 ? S 0:00 apache2 -DFOREGROUND
...
78 pts/0 Ss 0:00 /bin/sh
83 pts/0 R+ 0:00 ps -ax
Interactive Use
It’s not a good idea to specify an image in docker-compose.yml where no process is running permanently:
# File docker-compose.yml
# Container/service ends immediately because
# there is no background process
version: '3'
services:
myservice1:
image: alpine
If you now run docker compose up, the container in question will be set up and started, but the execution will again terminate immediately. The result looks similar when you run docker stack deploy, as the service gets set up and started, but due to the lack of a background process, its execution ends immediately. The swarm logic of Docker then keeps trying to start the service—but of course that doesn’t help either. The REPLICAS column of docker service ls shows 0/1, and docker service ps lists various startup attempts but shows no error messages:
docker stack deploy -c docker-compose.yml test4
docker service ls
ID NAME REPLICAS IMAGE
1t8cfulkpxoz test4_myservice1 0/ alpine:latest
docker service ps 1t8cfulkpxoz
ID ... DESIRED STATE CURRENT STATE ERROR
kolqn5wnzgp2 Ready Ready 4 seconds ago
yrzb2d0g4arc Shutdown Complete 10 seconds ago
y3ojrkgfv6sw Shutdown Complete a minute ago
y54o8unhqf8s Shutdown Complete a minute ago
xayg2njehiyj Shutdown Complete 2 minutes ago
It will probably be an exceptional case, but it is possible to create a container for interactive use with docker compose. To do this, you must use docker compose to execute the run <servicename> command instead of up. The additional option --rm causes the container to be deleted after exiting the shell.
docker compose run --rm myservice1
cat /etc/os-release
NAME="Alpine Linux"
PRETTY_NAME="Alpine Linux v3.13"
...
exit
There’s no comparable interactive usage variant for docker stack deploy because docker stack is based on the concept of services, and the interactive operation of a service doesn’t make any sense
Editor’s note: This post has been adapted from a section of the book Docker: Practical Guide for Developers and DevOps Teams by Bernd Öggl and Michael Kofler.
Comments