How to set up a Local development environment using docker compose

Share Now
4 min read
0
(0)
769

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 by up.
  • 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, and database 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/

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Leave comment

Your email address will not be published. Required fields are marked with *.