DEV Community

Cover image for Docker for Absolute Beginners — Part 8
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

Docker for Absolute Beginners — Part 8

Multi-Service Apps with Container-to-Container Communication

In Part 7, we learned how your local browser can communicate with a containerized Django app. Today, we level up—connecting multiple containers (frontend, backend, and database) with Docker Compose. We'll explore inter-container communication, database persistence, healthchecks, and dependency management.

🎯 Learning Aims

By the end of this post, you'll be able to:

  • Connect multiple Docker containers together (container-to-container communication).

  • Use docker-compose.yml to manage a multi-service app (frontend, backend, database).

  • Understand and implement Docker healthchecks.

  • Control service start order and dependencies with depends_on.

  • Persist data using Docker volumes.

1. About the Project

This project is a full-stack web application composed of a frontend and a backend service, all running in separate Docker containers. The frontend is a Next.js application, and the backend is a Django application. A MySQL database is also included as a service. The entire application is orchestrated using Docker Compose, which allows for easy setup and management of the multi-container application.

📂 Project Structure

project/
├─ backend/
│   ├─ Dockerfile
│   ├─ requirements.txt
│   ├─ manage.py
│   └─ authz/
│       ├─ urls.py
│       └─ views.py
├─ frontend/
│   ├─ Dockerfile
│   └─ src/
│       ├─ Register.js
│       ├─ Login.js
│       └─ ForgotPw.js
├─ docker-compose.yml
└─ .env
Enter fullscreen mode Exit fullscreen mode

🛢 Database (MySQL)

  • Image: Official MySQL (mysql:8.4)

  • Purpose: Persistent storage for user data and authentication.

  • Persist data: Docker named volume db_data.

🌐 Backend (Django API)

  • Routes:

    • /register/: Create user accounts.
    • /login/: Authenticate users, generate a token.
    • /forgot-password/: Generate password reset codes.
  • Uses MySQL database to store and retrieve user information.

🖥 Frontend (Next.js UI)

  • API Calls:

    • POST /register/
    • POST /login/
    • POST /forgot-password/

Communicates with backend to handle user authentication.

2. Docker Concepts to Learn

Frontend Dockerfile (frontend/Dockerfile)

FROM node:20-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm","run","dev"]
Enter fullscreen mode Exit fullscreen mode
  • FROM node:20-alpine: This command specifies the base image for our container. We are using the official Node.js image with version 20 on an Alpine Linux distribution. Alpine is a lightweight Linux distribution, which results in a smaller image size.
  • WORKDIR /usr/src/app: This sets the working directory inside the container to /usr/src/app. All subsequent commands will be executed from this directory.
  • COPY package*.json ./: This copies the package.json and package-lock.json files from the host machine to the current working directory (/usr/src/app) inside the container. These files contain the project's dependencies.
  • RUN npm install: This command installs the dependencies listed in the package.json file.
  • COPY . .: This copies the rest of the frontend application's source code into the container.
  • EXPOSE 3000: This informs Docker that the container will listen on port 3000 at runtime.
  • CMD ["npm", "run", "dev"]: This specifies the command to run when the container starts. In this case, it starts the Next.js development server.

Backend Dockerfile (backend/Dockerfile)

FROM python:3.12-slim
WORKDIR /usr/src/app

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        build-essential         \
        default-libmysqlclient-dev \
        pkg-config && \
    rm -rf /var/lib/apt/lists/*

COPY requirements.txt ./
RUN pip install --upgrade pip && pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
Enter fullscreen mode Exit fullscreen mode
  • FROM python:3.12-slim: This sets the base image to a slim version of Python 3.12.
  • WORKDIR /usr/src/app: Sets the working directory inside the container.
  • RUN apt-get update && ...: This command block updates the package lists and installs necessary packages for the application, such as build-essential and default-libmysqlclient-dev, which are required for building some of the Python dependencies. rm -rf /var/lib/apt/lists/* is used to clean up and reduce the image size.
  • COPY requirements.txt ./: Copies the requirements.txt file, which lists the Python dependencies.
  • RUN pip install --upgrade pip && pip install -r requirements.txt: Upgrades the package installer for Python and then installs the application's dependencies.
  • COPY . .: Copies the backend application's source code into the container.
  • EXPOSE 8000: Informs Docker that the container will listen on port 8000.
  • CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]: The command to run when the container starts, which is the Django development server.

Docker Compose (docker-compose.yml)

services:

  database:
    image: mysql:8.4
    container_name: mysql_db
    env_file: .env
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 5s
      timeout: 3s
      retries: 5

  backend:
    build: ./backend
    container_name: django_api
    env_file:
      - .env
      - ./backend/.env
    command: python manage.py runserver 0.0.0.0:${BACKEND_PORT}
    ports:
      - "${BACKEND_PORT}:${BACKEND_PORT}"
    volumes:
      - ./backend:/usr/src/app
      - /usr/local/lib/python3.12/site-packages
    depends_on:
      database:
        condition: service_healthy

  frontend:
    build: ./frontend
    container_name: next_ui
    env_file:
      - .env
      - ./frontend/.env
    ports:
      - "${HOST_PORT_FRONTEND}:3000"
    volumes:
      - ./frontend:/usr/src/app
      - /usr/src/app/node_modules
    depends_on:
      backend:
        condition: service_started

volumes:
  db_data: {}
Enter fullscreen mode Exit fullscreen mode
  • services: This is the main section where we define the different services that make up our application.
  • database: This is the name of our database service.
    • image: mysql:8.4: Specifies that this service will use the official MySQL image, version 8.4.
    • container_name: mysql_db: Assigns a specific name to the container for easier identification.
    • env_file: .env: Loads environment variables from a .env file in the root directory.
    • volumes: - db_data:/var/lib/mysql: Mounts a named volume db_data to the /var/lib/mysql directory in the container. This is to persist the database data even if the container is removed.
    • healthcheck: Defines a command to check if the database is healthy and ready to accept connections.
  • backend: This is our Django backend service.
    • build: ./backend: Specifies that Docker Compose should build the image for this service using the Dockerfile in the backend directory.
    • container_name: django_api: A specific name for the backend container.
    • env_file: Loads environment variables from .env files.
    • command: Overrides the default command in the Dockerfile.
    • ports: - "${BACKEND_PORT}:${BACKEND_PORT}": Maps a port on the host machine to a port in the container.
    • volumes: Mounts the backend source code into the container for live reloading and creates an anonymous volume for the Python packages.
    • depends_on: Specifies that the backend service depends on the database service. It will wait until the database is healthy before starting.
  • frontend: This is our Next.js frontend service.
    • build: ./frontend: Builds the image from the frontend directory's Dockerfile.
    • container_name: next_ui: A specific name for the frontend container.
    • ports: - "${HOST_PORT_FRONTEND}:3000": Maps the host port to the container's port 3000.
    • volumes: Mounts the frontend source code for live reloading and creates an anonymous volume for node_modules.
    • depends_on: The frontend service will start after the backend service has started.
  • volumes: db_data: {}: Defines the named volume db_data used by the database service.

3. How to Run and Test the Project

To run the project, you need to have Docker and Docker Compose installed.

  1. Build and run the containers:

    docker-compose up --build
    
- **`docker-compose`**: The command-line tool for running multi-container Docker applications.
- **`up`**: This command builds, (re)creates, starts, and attaches to containers for a service.
- **`--build`**: This flag tells Docker Compose to build the images before starting the containers.
Enter fullscreen mode Exit fullscreen mode
  1. Access the application:

  2. To stop the application:

    docker-compose down
    
- **`down`**: This command stops and removes the containers, networks, volumes, and images created by `up`.
Enter fullscreen mode Exit fullscreen mode

4. Conclusion

From this project, you have learned:

  • How to create Dockerfiles for both frontend (Node.js) and backend (Python/Django) applications.
  • How to use Docker Compose to define and run a multi-container application.
  • How to connect services using Docker networks and depends_on.
  • How to use volumes to persist data and enable live code reloading.
  • How to use environment variables to configure your application within Docker.

Congratulations on completing all eight parts of our Docker series! Let’s revisit the journey you’ve taken:

  1. Part 1 introduced container fundamentals and got you up and running with Docker CLI.
  2. Part 2 showed you how to package a Node/Express app into a container.
  3. Part 3 supercharged your development loop with live-reload and volumes.
  4. Part 4 taught you multi-stage builds to produce lean, production-ready images.
  5. Part 5 walked you through debugging techniques inside an active container.
  6. Part 6 demonstrated secure, flexible configuration management via environment variables.
  7. Part 7 combined your front-end and back-end into a cohesive Docker Compose setup.
  8. Part 8 culminated in a multi-service architecture, exploring custom networks and service orchestration.

You now have a solid foundation in containerizing applications, optimizing images, debugging, and composing complex environments. From here, you might explore:

  • CI/CD with Docker: Automate builds and deployments in Jenkins, GitHub Actions, or GitLab CI.
  • Container Security: Scan images for vulnerabilities and follow best practices to lock down your containers.
  • Orchestration with Kubernetes: Take your Docker skills to production scale with Pods, Deployments, and Services.

Thank you for joining this series—happy Dockering, and may your containers always start on the first try! 🚀


Enter fullscreen mode Exit fullscreen mode

Top comments (0)