Mastering Docker Compose for Local Development and Testing

Let’s face it: developing and testing applications in a local environment that bears no resemblance to production is like practicing for a marathon on a treadmill. Sure, you’re running, but…

To truly test your application, your local environment needs to simulate production as closely as possible: complete with databases, networks, microservices, Redis, and whatever else your app depends on.

In this article, we’ll walk through how to use Docker Compose to create a local development environment that mirrors production. Because if you’re not testing in an environment that’s at least 90% accurate, you’re just lying to yourself.

The Microservices Configuration

All my microservices live in a folder called the-project. Each microservice has its own folder with a Dockerfile, because I’m a responsible developer who believes in containerization. Here’s what the setup looks like:

the-project/
├── microservice-1/
│   ├── Dockerfile
│   └── ...
├── microservice-2/
│   ├── Dockerfile
│   └── ...
├── microservice-3/
│   ├── Dockerfile
│   └── ...
├── microservice-4/
│   ├── Dockerfile
│   └── ...
├── microservice-5/
│   ├── Dockerfile
│   └── ...
└── docker-compose.yml

My goal is to create a docker-compose.yml file at the root of the-project that spins up:

  • All five microservices.
  • A database with separate schemas for each microservice (because sharing is overrated).
  • A Redis server (because caching is life).
  • A bucket (because every app needs a place to dump files).
  • Network configurations to ensure some microservices are publicly accessible while others are not.

The Docker-Compose File

Here’s where I define my docker-compose.yml file. This file is the blueprint for my local environment, and it’s where I specify how all the services interact. Let’s break it down:

version: '3.8'

services:
  microservice-1:
    build: ./microservice-1
    ports:
      - "8081:8080"
    networks:
      - public
      - private
    depends_on:
      - redis
      - database
      - bucket

  microservice-2:
    build: ./microservice-2
    ports:
      - "8082:8080"
    networks:
      - private
    depends_on:
      - redis
      - database
      - bucket

  # Repeat for microservice-3, microservice-4, and microservice-5

  redis:
    image: redis:latest
    ports:
      - "6379:6379"
    networks:
      - private

  database:
    image: postgres:latest
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: password
      POSTGRES_DB: the-project
    ports:
      - "5432:5432"
    networks:
      - private

  bucket:
    image: minio/minio:latest
    ports:
      - "9000:9000"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    volumes:
      - minio-data:/data
    networks:
      - private

networks:
  public:
  private:

volumes:
  minio-data:

Key Points:

  • build: Specifies the path to the Dockerfile for each microservice.
  • ports: Maps container ports to host ports. Publicly accessible microservices get unique ports.
  • networks: Segregates services into public and private networks for security.
  • depends_on: Ensures services like Redis, the database, and the bucket start before the microservices.

Unit Testing

Now, let’s talk about unit testing. If you’re using the same docker-compose.yml for development and testing, you’ll need to make a few tweaks to avoid conflicts and streamline the process. Here’s what you should do:

  1. Update Ports: Use different ports for testing to avoid conflicts with your development environment. For example:
microservice-1:
  ports:
  - "9081:8080"
  1. Only Run Necessary Services: For unit tests, you don’t need all the microservices running. Just spin up Redis, the database, and the bucket:
docker-compose up redis database bucket
  1. Use Test-Specific Configurations: Override environment variables or configurations in your docker-compose.yml to point to test-specific resources. For example:
environment: 
  SPRING_PROFILES_ACTIVE: test

By isolating the services needed for testing, you’ll reduce overhead and avoid the error “port already in use” error.

Conclusion

If your local environment doesn’t closely resemble production, you’re not testing: you’re guessing. And while guessing might work for lottery tickets, it’s a terrible strategy for software development.

Docker Compose allows you to create a local environment that’s as close to production as possible, reducing the risk of “it works on my machine” syndrome.

By investing time in setting up a robust local environment, you’ll shorten your test loop, speed up delivery, and maybe even sleep better at night.


Never Miss Another Tech Innovation

Concrete insights and actionable resources delivered straight to your inbox to boost your developer career.

My New ebook, Best Practices To Create A Backend With Spring Boot 3, is available now.

Best practices to create a backend with Spring Boot 3

Leave a comment

Discover more from The Dev World - Sergio Lema

Subscribe now to keep reading and get access to the full archive.

Continue reading