Mastering Local Development with Docker Compose

Sebastian Pawlaczyk
DevBulls

--

In the rapidly evolving world of software development, managing and orchestrating multiple services efficiently is crucial. Docker Compose and Kubernetes are two powerful tools that facilitate this process, each serving different purposes and environments. This article aims to provide a comprehensive guide to Docker Compose, emphasizing its strengths in local development while comparing it to Kubernetes, a tool designed for production environments.

Docker Compose vs Kubernetes

To better understand the distinctions between Docker Compose and Kubernetes, let’s first look at their high-level architectures.

Docker Compose Architecture:

  • Simple, single-node orchestration.
  • Designed for local development and testing.
  • Uses a YAML file (docker-compose.yml) to define services, networks, and volumes.
  • Quick and easy setup with minimal configuration.

Kubernetes Architecture:

  • Complex, multi-node orchestration.
  • Built for production-scale deployments.
  • Manages clusters of nodes, handling scaling, load balancing, and failover.
  • Requires more extensive configuration and management.

The Docker Compose File

The configuration is defined in adocker-compose.yaml file. It allows you to configure and manage multiple Docker containers easily. Let’s dive into the core components:

1. Services:

  • Represents the application deployments.
  • Each service runs a specific image, which can be a pre-built image or built from a Dockerfile.
  • You can define environment variables, port mappings, dependencies, and more for each service.

2. Networks:

  • Define the isolation tier.
  • Services can communicate with each other over defined networks.
  • By default, Docker Compose creates a bridge network for all services, but you can create custom networks if needed.

3. Volumes:

  • Provide persistent storage.
  • Volumes allow data to persist even when containers are recreated or removed.
  • You can define named volumes that can be shared between services.
services:
wordpress:
image: wordpress
ports:
- "8080:80"
depends_on:
- mysql
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_USER: root
WORDPRESS_DB_PASSWORD: "admin"
WORDPRESS_DB_NAME: wordpress
networks:
- wordpress-tier
mysql:
image: mysql:5.7
platform: linux/amd64
environment:
MYSQL_DATABASE: wordpress
MYSQL_ROOT_PASSWORD: "admin"
volumes:
- ./mysql:/var/lib/mysql
networks:
- wordpress-tier

networks:
wordpress-tier: { }

wordpress service:

  • Maps port 8080 on the host to port 80 on the container, making the WordPress site accessible at http://localhost:8080.
  • Depends on the mysql service, ensuring that the database starts before the WordPress service.
  • Sets environment variables needed to configure the WordPress connection to the database.
  • Connects to the wordpress-tier network for communication with the mysql service.

mysql service:

  • Sets environment variables for database initialization, such as the database name and root password.
  • Uses a bind mount volume ./mysql to persist MySQL data on the host machine.
  • Connects to the wordpress-tier network to allow communication with the wordpress service.

wordpress-tier:

  • A custom network for isolating the WordPress and MySQL services. By defining this network, the services can communicate with each other while remaining isolated from other containers.

Building from a Dockerfile

A useful feature of Docker Compose is the ability to build images from a Dockerfile directly within the docker-compose.yaml file. This is particularly handy when you need to customize your container images. Here’s a short snippet demonstrating this:

services:
app:
build:
context: . # path to a directory containing a Dockerfile
dockerfile: Dockerfile # sets an alternate Dockerfile

Commands

List of necessary commands.

Starting the services:

  • docker-compose up - run containers
  • docker-compose up -d - run containers in the background
$ docker-compose up -d  

Network docker-compose-devbulls_wordpress-tier Created
Container docker-compose-devbulls-mysql-1 Started
Container docker-compose-devbulls-wordpress-1 Started

Viewing the running services:

  • docker-compose ps - show running containers only for your configuration
  • docker ps - show all running containers
$ docker-compose ps

NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
docker-compose-devbulls-mysql-1 mysql:5.7 "docker-entrypoint.s…" mysql About a minute ago Up About a minute 3306/tcp, 33060/tcp
docker-compose-devbulls-wordpress-1 wordpress "docker-entrypoint.s…" wordpress About a minute ago Up About a minute 0.0.0.0:8080->80/tcp
$ docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7963c297a60e nginx:latest "/docker-entrypoint.…" 57 seconds ago Up 57 seconds 80/tcp festive_lehmann
fcc2c16b9422 wordpress "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:8080->80/tcp docker-compose-devbulls-wordpress-1
ff0625fae9d4 mysql:5.7 "docker-entrypoint.s…" About a minute ago Up About a minute 3306/tcp, 33060/tcp docker-compose-devbulls-mysql-1

Stopping and removing:

  • docker-compose down - stop and remove containers and networks
  • docker-compose down -v - additionally remove volumes
$ docker-compose down -v

Container docker-compose-devbulls-wordpress-1 Removed
Container docker-compose-devbulls-mysql-1 Removed
Network docker-compose-devbulls_wordpress-tier Removed

Professional Experience

In my professional experience, Docker Compose has been an invaluable tool for setting up local development environments. Primarily, I have created single Docker Compose files to deploy essential services like databases or message queues. However, there have been more complex scenarios where multiple services were required to run a single application for testing purposes.

Let’s imagine a microservice architecture. I want to test Service-A locally, but for specific testing purposes, I need to have Service-B and Service-C running as well. Additionally, Service-B needs a database instance and a message queue. Such a configuration, when combined into a single file, can become very complicated to maintain. Based on our previous example, I will demonstrate the idea, but note that it will be overkill for such a simple deployment.

Multiple configuration file

project
├── mysql-service
│ ├── docker-compose.yaml
├── wordpress-service
│ └── docker-compose.yaml
└── docker-compose.yaml
# mysql-service/docker-compose.yaml
services:
mysql:
image: mysql:5.7
platform: linux/amd64
environment:
MYSQL_DATABASE: wordpress
MYSQL_ROOT_PASSWORD: "admin"
volumes:
- ./mysql:/var/lib/mysql
networks:
- wordpress-tier

networks:
wordpress-tier: { }
# wordpress-service/docker-compose.yaml
services:
wordpress:
image: wordpress
ports:
- "8080:80"
depends_on:
- mysql
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_USER: root
WORDPRESS_DB_PASSWORD: "admin"
WORDPRESS_DB_NAME: wordpress
networks:
- wordpress-tier

networks:
wordpress-tier: { }
# docker-compose.yaml
include:
- wordpress-service/docker-compose.yaml
- mysql-service/docker-compose.yaml

Benefits of This Approach

  • Modularity: Each service has its own Dockerfile and Docker Compose file, making it easier to manage and update independently.
  • Scalability: You can add more services as needed without modifying a single large Docker Compose file.
  • Isolation: Each service can be developed and tested in isolation, then combined for integration testing

Conclusion

Creating Docker Compose files early in the development process and maintaining them throughout the project lifecycle is crucial. This practice ensures that your local development environment closely mirrors your production setup, leading to more reliable and efficient development and testing processes. By leveraging Docker Compose for both simple and complex configurations, you can significantly enhance your workflow and collaboration within your team. When I see a well-configured Docker Compose file, it’s a sure sign that coding here will be a joy.

--

--