Skip to the content.

Docker Compose

Docker Compose was primarily designed to facilitate the orchestration of multi-container applications

It simpifies starting & connecting containers & other resources together.

To elaborate, follow the example below.

Multi Container App

Let’s build a multi-container counterapp with expressjs & redis to track the number of times the api is called.

{
  "name": "node-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node app.mjs"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.2",
    "redis": "^4.6.7"
  }
}

b. app.mjs

import express from 'express'
import redis from 'redis'

const app = express()

const { REDIS_URL = 'redis://localhost:6379' } = process.env;
console.log(`Redis URL: ${REDIS_URL}`)

const redisClient = redis.createClient({
    url: REDIS_URL
})

app.get('/', async (_req, res) => {
    try {
        let visits = await redisClient.get('visits')
        visits = parseInt(visits)
        await redisClient.set('visits', visits + 1)
        res.json({ noOfVisits: visits })
    } catch (err) {
        console.log(err)
        res.status(500).json({ message: 'server error'})
    } 
})

const PORT = process.env.PORT || 3001
app.listen(PORT, async () => {
    console.log(`Server is listening on port ${PORT}`)
    try{
        await redisClient.connect()
        await redisClient.set('visits', 0)
        console.log('Connected to Redis server')
    } catch (err) {
        console.log(err)
    }
})

This app connects with a redis-server running locally at 6379 to track the api count

Redis as a Docker Container

docker run -d --rm --name redis-container -p 6379:6379 redis

Run App on your Machine Terminal

npm start

App Containerization

# Specify base image
FROM node:18-alpine

WORKDIR /app

# Copy package.json & install dependencies
COPY package.json .
RUN npm install --production

# Copy rest of the code
COPY . .

CMD npm start
docker build -t counterapp .

Run App Without Docker Compose

In this sceanrio, we will run app using docker-cli only.

Running App as a container

docker run --rm --name counterapp -p 3001:3001 counterapp
> node-app@1.0.0 start
> node app.mjs

Server is listening on port 3001
Error: connect ECONNREFUSED 127.0.0.1:6379
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1495:16) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 6379
}

Docker Network

docker network create counterapp-network

Connect Redis to Network

a. Connect existing container:

docker network connect counterapp-net redis-container

b. Connect while creating container:

docker run -d --rm --name redis-container --network counterapp-net -p 6379:6379 redis

Connect App

docker run --rm --name counterapp -p 3001:3001 --network=counterapp-net --env REDIS_URL=redis://redis-container:6379 counterapp

Debugging network

docker network inspect counterapp-net

Summary

Run App With Docker Composer

version: '3'
services:
  redis-container:
    image: 'redis:latest'

  counterapp:
    image: 'counterapp'
    build: .
    ports:
      - '3001:3001'
    environment:
      - REDIS_URL=redis://redis-container:6379
    depends_on:
      - redis-container
docker compose up

In essence, docker-compose does the heavy lifting of creating a network & connecting containers to it.

Docker Compose Primer

  1. Docker Compose is a tool primarily used orchestrating multi-container Docker applications
  2. It could be used to run single container apps as well where all the inputs like environment variables, ports, volumes, etc. are defined in the docker-compose.yml file
  3. Whatever the case, command to start the app is docker compose up
  4. Unlike docker cli, there is no need to pass the --rm flag to remove the container after it exits

If you’re accustomed to using individual Docker commands to manage your containers, Docker Compose will streamline this process, allowing you to define entire applications in a single file.

Key Features

  1. Simplified Management: Write a docker-compose.yml file and start your entire stack with docker-compose up.

  2. Enhanced Development Workflow: Share the same docker-compose.yml across your team, ensuring everyone is using the same environment.

  3. Encapsulation of Configuration: Use different compose files for different environments like development and production.

Benefits of Docker Compose

Key Commands

docker compose up

The docker-compose up command is used to build, (re)create, start, and attach to containers for a service defined in a docker-compose.yml file.

docker compose up
Usage Description Command Explanation
Basic Start docker-compose up Starts all the services defined in the docker-compose.yml file.
Detach Mode docker-compose up -d Runs containers in the background.
Specific Service docker-compose up service_name Only starts the specified service and its dependencies.
Build and Start docker-compose up --build Builds the images before starting the containers.
Scale a Service docker-compose up --scale service_name=3 Starts multiple instances of a service.
Force Recreate docker-compose up --force-recreate Forces recreating containers even if they are up-to-date.
Remove Orphans docker-compose up --remove-orphans Removes containers for services not defined in the current configuration.

This table neatly organizes the different ways you can use the docker-compose up command for various purposes.

docker compose down

docker compose down

Description: Stops containers and removes containers, networks, volumes, and images created by up.

By default, the only things removed are:

Anonymous volumes are not removed by default. However, as they don’t have a stable name, they will not be automatically mounted by a subsequent up.

For data that needs to persist between updates, use explicit paths as bind mounts or named volumes.

Docker Compose vs K8s

This table succinctly highlights the differences between Docker Compose and Kubernetes, showing their respective use cases and functionalities.

Aspect Docker Compose Kubernetes
Primary Use Development & testing environments Large-scale, production environments
Complexity Simpler, suitable for local development Complex, for managing distributed systems
Configuration YAML file for services, networks, volumes YAML/JSON for pods, services, volumes, etc.
Scalability Limited scalability Highly scalable, for large deployments
Availability No built-in high availability High availability, load balancing, auto-scaling
Portability Mostly local; limited portability Across various cloud providers and on-premises
Development to Production Workflow Used for local development Used for production deployment

Nothing explains better than a picture:

infra diff for compose vs k8s

References

Compose Basics

Stepher Grider Course Reference

Compose vs K8s