DevOps

Getting Started with Docker Compose

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.

Recommendation

Docker
Docker

Learn the ins and outs of containerization in Docker with this practical guide! Begin by installing and setting up the platform. Then master the basics: get to know important terminology, understand how to run containers, and set up port redirecting and communication. You’ll learn to create custom images, work with commands, and use key containerization tools. Gain essential skills by following exercises that cover common tasks from packaging new applications and modernizing existing applications to handling security and operations.

Learn More
Rheinwerk Computing
by Rheinwerk Computing

Rheinwerk Computing is an imprint of Rheinwerk Publishing and publishes books by leading experts in the fields of programming, administration, security, analytics, and more.

Comments