Baeldung Pro – Ops – NPI EA (cat = Baeldung on Ops)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

1. Overview

Maintaining multiple containers with complex configurations can become difficult if we’re relying on the docker run command. Conveniently, Docker Compose provides a solution by enabling us to define multi-container Docker applications using a YAML configuration file.

In this tutorial, we’ll demonstrate how to convert a docker run command into a docker-compose.yml file.

2. Basics Structure of the docker run Command

The docker run command is useful for creating and running containers:

$ docker run -d \
  --name container_name \
  -p host_port:container_port \
  -e ENV_VAR=value \
  -v /host/path:/container/path \
  --network my-network \
  image_name:tag

Let’s break down this command:

  • -d runs the command in detached mode
  • –name container_name names the container
  • -p host_port:container_port maps a port on the host to a port on the container
  • -e ENV_VAR=value sets an environment variable in the format KEY=value
  • -v /host/path:/container/path mounts a volume from a host to a container
  • –network my-network connects the container to a network
  • image_name:tag specifies the image and tag to use

Now, let’s utilize the syntax to create and run the container my-container:

$ docker run -d \
  --name my-container \
  -p 8080:80 \
  -e APP_ENV=production \
  -v /var/www/html:/usr/share/nginx/html \
  --network my-network \
  nginx:latest

Implementing the docker run command is straightforward for simple cases but it can become repetitive and prone to errors for complex cases. Because of this, Docker Compose is often preferred.

3. Basic Structure of a Compose File

To begin with, let’s see the general structure of the Compose file:

$ cat docker-compose.yml
version: '3.9'
services:
  service_name:
    image: image_name:tag
    container_name: container_name
    ports:
      - "host_port:container_port"
    environment:
      - ENV_VAR=value
    volumes:
      - /host/path:/container/path
    networks:
      - network_name
networks:
  network_name:
    driver: bridge

Now, let’s use the above structure to translate the docker run command that we provided earlier:

$ cat docker-compose.yml
version: '3.9'
services:
  my-container:
    image: nginx:latest
    container_name: my-container
    ports:
      - "8080:80"
    environment:
      - APP_ENV=production
      - DB_HOST=db.example.com
    volumes:
      - /var/www/html:/usr/share/nginx/html
    networks:
      - my-network
networks:
  my-network:
    driver: bridge

So, let’s see what each directive does:

  • version specifies the version of the Compose file format, in this case, version 3.9 which is commonly utilized with modern Docker versions
  • services is a section that can define each container whereby each service is specified in a way similar to the options in the docker run command
  • image defines the Docker image nginx and the tag latest
  • container_name names the container my-container
  • ports maps host ports to container ports, in this case, 8080 to 80
  • environment is a key meant to specify the environment variables APP_ENV and DB_HOST
  • volumes mounts the /var/www/html directory on the host machine to /usr/share/nginx/html inside the container
  • networks connects containers to networks

Usually, it’s more secure to define the environment variables in a .env file instead of hardcoding it in the Compose file.

4. Advantages of Docker Compose

Let’s discuss a few advantages Docker Compose provides over using the docker run command.

4.1. Additional Configuration Options

Docker Compose supports other essential configurations we can implement like service dependencies:

depends_on:
  - database

This configuration ensures that containers start in the correct order.

Additionally, we can utilize the restart configuration:

restart: always

With this configuration, failed containers are automatically restarted.

Another option we can utilize is monitoring the health of a service:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost"]
  interval: 30s
  retries: 3

In this case, we instruct Docker to regularly check whether a service is reachable by sending a request to http://localhost. If the service is unresponsive, Docker tries to restart the container after a few failed attempts.

4.2. Improves Container Management

Docker Compose enables us to reduce complexity and errors since we can define all configurations in a single YAML file instead of using repetitive, complex, and multiple docker run commands. For example, teams can easily add new services, modify existing ones without manually modifying and restarting containers, and update configurations. It enables us to restart, stop, or inspect logs of multiple services enhancing debugging.

4.3. Scalability

With Docker Compose, we can more easily scale services. Specifically, we can use the scale option to define the number of container instances for a specific service.

$ docker-compose up --scale my-container=3

In this example, Docker Compose starts 3 container instances for the service my-container.

4.4. Easy Networking

The containers we define inside the same docker-compose.yml file can communicate with each other using their service names. Thus, Docker Compose removes the need to manually set up network connections making it more straightforward for containers to communicate. Typically, Docker Compose creates a default network in which all the services within the same Compose file can communicate without the need for additional configuration.

4.5. Portability and Collaboration

The docker-compose.yml file defines the services, configurations, and dependencies. It makes it easier for us to share and apply version control to the file, ensuring consistency, reproducibility, and easier collaboration across different environments.

4.6. Easy Execution of the Docker Compose File

To demonstrate, let’s use Docker Compose v2 which utilizes docker compose instead of docker-compose.

Now, we can start containers using a single command:

$ docker compose up -d

To stop and remove the containers specified in the docker-compose.yml file, we just use down:

$ docker compose down

After making any changes to the Compose file, we add –build:

$ docker compose up -d --build

Thus, we rebuild the container images before starting the containers.

4.7. Better Integration With Advanced Solutions

Docker Compose integrates better with orchestration tools like Kubernetes. Hence, with a well-structured docker-compose.yml file, we can easily transition to more advanced container management solutions. For DevOps, we can leverage Docker Compose to include CI/CD pipelines and general deployment automation steps into workflows.

5. Conclusion

In this article, we saw how to convert a docker run command into a docker-compose file.

First, we discussed the basic structure of the docker run command along with that of a Compose file. After that, we looked at many advantages that Docker Compose offers over the docker run command. For instance, we get additional options helpful for configuration along with a better experience when working with multiple containers.