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 theDockerfilefor 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:
- Update Ports: Use different ports for testing to avoid conflicts with your development environment. For example:
microservice-1:
ports:
- "9081:8080"
- 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
- Use Test-Specific Configurations: Override environment variables or configurations in your
docker-compose.ymlto 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.



Leave a comment