How to set up a Local development environment using docker compose
What is Docker Compose and How Does it Improve Local Development?
Docker Compose simplifies multi-container application management by using a single YAML file to define and orchestrate
- services (individual containers),
- networks (docker networks with defined driver like bridge, host), and
- storage (docker volume or bind mounts) for Docker containers.
The benifit of docker compose: According to me, no one can explain it better than its own documentation, to know more about its benefit, we can read it from https://docs.docker.com/compose/intro/features-uses/
This writeup will attempt to get you started with Docker Compose, covering foundational concepts, key blocks, and essential commands for a seamless local development experience.
Below is an example of how to create a mind map for managing an application with this Compose setup.
This approach helps organize the dependencies between services, understand traffic flow, and determine the essential port mappings.
In the example below, I’ve outlined a 3-tier MERN application to illustrate local development planning:
- React will receive traffic on port 3001 and will need to connect to Express. Since React relies on the Express base URL, we should pass the Express base URL to React, which can be managed via the environment block for the React service.
- Similarly, Express needs to connect to Mongo, so Mongo-related information should be passed to Express through the environment configuration.
I hope this provides a clear and structured approach to setting up your environment.
Lets dive in.

1. Getting Started with YAML in Docker Compose
Docker Compose uses a YAML file (compose.yaml
) to define the application’s services. YAML is human-readable, structured with maps (key-value pairs) and lists (sequences of items), which makes it intuitive for configuring services.
A simple docker compose yaml file looks something like this
version: '3.8' # For newer compose, adding the version is not mandatory
services: # Main block where each service (container) is defined
app:
image: node:18
ports:
- "3000:3000"
environment:
- NODE_ENV=development
database:
image: mongo:latest
ports:
- "27017:27017"
2. Understanding Key Blocks in Docker Compose
– Services Block
The services
block is where we define each component of our application as a service (container).
Each service has sub-blocks that specify configuration options, including build
, environment
, volumes
, networks
, and ports
.
services:
app:
image: daspratha/mern # This can be used to tag the image and that would be used to push that later on the registry
build: # Optional, it is needed only to build from a Dockerfile
context: . # Build context, usually set to current directory
dockerfile: Dockerfile
ports:
- "3000:3000" # Maps host port 3000 to container port 3000
environment:
- DATABASE_URL=mongodb://database:27017/mydb
depends_on:
- database # its the name of the db service(for this we kept it as database), check the next line, it Ensures 'database' service starts before 'app'
database:
image: mongo:latest
ports:
- "27017:27017"
In the above example:
build
specifies the Dockerfile and context if building locally.ports
defines port mappings from the container to the host.environment
provides environment variables to the container.
– Networks Block
The networks
block creates a custom networks to control communication between containers. Docker Compose automatically creates an isolated network for services, but we can create named networks for greater control.
Here, we are creating network by the name “internal-network” and its using “dridge” driver, that means port mapping is needed to reach the application inside container from host machine.
networks:
internal-network:
driver: bridge
Once the network is created (make sure, it should be aligned on the same vertical line as services) we can assign each service(container) with the network, for smooth communicate among them:
services:
app:
networks:
- internal-network
database:
networks:
- internal-network
– Volumes Block
The volumes
block is used to persist data. Named volumes are managed by Docker, while bind mounts map a directory on the host to a directory in the container.
Example of named volume:
volumes:
app-data: # Named volume to store data persistently
Usage within a service:
services:
app:
volumes:
- app-data:/usr/src/app/data # Maps the volume to a path in the container
database:
volumes:
- mongo-data:/data/db # MongoDB data persistence
volumes:
mongo-data:
– Environment Block
The environment
block defines environment variables that are passed to the container when it starts. These are often used for configuration, such as database URLs or API keys.
services:
app:
environment:
- NODE_ENV=development
- DATABASE_URL=mongodb://database:27017/mydb
3. Advanced Options
– Docker Compose Commands
docker-compose up
: Builds, (re)creates, and starts the services. Use-d
to run in detached mode.docker-compose down
: Stops and removes containers, networks, volumes, and images created byup
.docker-compose build
: Builds or rebuilds services.docker-compose push
: Pushes service images to a registry (useful for deployment).docker-compose logs
: Displays log output from services.
– Environment Files
To avoid hardcoding sensitive information, like we did above, we can store environment variables in a .env
file and reference them in docker-compose.yaml
using ${VARIABLE_NAME}
syntax.
Example .env
file:
DATABASE_URL=mongodb://database:27017/mydb
NODE_ENV=development
Reference in docker-compose.yaml
:
services:
app:
environment:
- NODE_ENV=${NODE_ENV}
- DATABASE_URL=${DATABASE_URL}
– Depends_On
depends_on
specifies dependencies between services, ensuring one service starts before another. It does not guarantee readiness (e.g., database connections), so you might need additional scripts or tools like wait-for-it
for production setups.
services:
app:
depends_on:
- database
database:
image: mongo:latest
4. Complete Example: React + Express + MongoDB
Here’s a complete example combining React, Express, and MongoDB with Docker Compose:
version: '3.8'
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3001:3000"
environment:
- REACT_APP_API_URL=http://backend:3000
depends_on:
- backend
networks:
- app-network
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- MONGO_URL=mongodb://database:27017/mydb
- PORT=3000
depends_on:
- database
networks:
- app-network
database:
image: mongo:latest
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
mongo-data:
This setup:
- Defines
frontend
,backend
, anddatabase
services. - Connects services on a shared network (
app-network
). - Maps MongoDB data to a volume (
mongo-data
). - Uses environment variables to configure each service.
So, what we have learnt from the above? Its an essential tool for local development and testing. Hope this helps to setup the local development setup based on the application need.
If you have read upto this and enjoying the detailing and content, you like to know how to create private repositories using nexus https://www.liainfraservices.com/blog/how-to-create-private-repositories-in-nexus/

Partho Das, founder of Lia Infraservices, has 15+ years of expertise in cloud solutions, DevOps, and infrastructure security. He provides consultation on architecture planning, DevOps setup, Kubernetes, and cloud migrations. Partho holds multiple AWS and Azure certifications, along with CISCO CCNA & CCNP.
Connect on LinkedIn