Running Multiple Containers Using Docker Compose
Published: 2024-07-17 08:38:03

This tutorial gives you a step-by-step guide on how to run a multi-container web application using docker-compose. The program is composed of distinct services for the database (PostgreSQL), frontend (React), and backend (Python). Nginx serves as a security and efficiency reverse proxy.

## **Comprehending the Elements:

**
Backend:   Built using Python (uvicorn), served on port 8000. 
Frontend:  Built with React, served on port 5173.
Database:  PostgreSQL, setup with the database name and credentials.
Traefik:   Reverse proxy for traffic management and request routing to the 
           relevant service

## Requirements
:
A cloud instance having Docker installed, such as an AWS EC2.
The instance has npm and node.js installed.
An account on Docker Hub.
A unique domain name.

## Steps.
 

## 1. Launch an Instance with the following requirement

* AMI (Ubuntu)
* Instance type (T3 medium)
* Create new key pair
* Security group (allow ssh, http, https from anywhere)
* Storage size (1x 30)
* Launch Instance

## 2. Clone the repository

https://github.com/hngprojects/devops-stage-2
clone repository on link above to your git by forking it.
Create a new repository name e.g (new-repo)

 

## 3.Connect your instance to VSCode or Instance connect

Update your instance 

```
sudo apt update

```
install git

```
sudo apt install git

```
 
Clone git repository to your instance by using 

```
git clone https://github.com/your-username/new-repo

```

## 4. Set up the Frontend (from terminal)

```
cd new-repo

```

```
cd frontend

```
Set up npm and Node.js.

```
sudo apt install nodejs npm

```

```
sudo npm install -g n

```

```
sudo n stable

```
check the version 

```
node -v

```

```
npm -v

```

```
npm fund

```
ls (to see files created) or simply 

```
cat package.json 

```
```
cat vite.config.ts

```

```
nano vite.config.ts

```
_NB: each of these commands will allow you see what the files contains, and "nano" command allow you edit the files._

Paste this in the vite.config.ts file
```
export default defineConfig({
  plugins: [react(), TanStackRouterVite()],
  server: {
    host: '0.0.0.0',  // Listen on all interfaces
    port: 5173,       // Use port 5173 (can be changed if needed)
  },
});
```
The code above Modified the vite.config.ts file so that port 5173 is accessible.

   Execute npm run dev and npm install.
```
npm run dev
```

```
npm install
```

-- Configure the security group for your instance to accept incoming traffic on port 5173. Using port 5173 and your instance's public IP address, access the frontend.

## 5. Make the Frontend Containerized:

* In the frontend directory, create a Dockerfile. Build the React application, expose port 80, install dependencies, use the official Node.js image, and copy the application code.
   * Use docker build to create the Docker image.

Install Docker
```
sudo apt install docker
```
```
start docker
```
```
sudo systemctl start docker
```
```
sudo systemctl enable docker
```

Create a new docker file in the frontend folder
```
nano dockerfile
```
then paste this 
```
Use an official Node.js image as the base
FROM node:20

Set the working directory
WORKDIR /app

Copy package.json and install dependencies
COPY package*.json ./
RUN npm install

Copy the rest of the application files
COPY . .

Build the React app
RUN npm run build

Expose port 80 and start the application
EXPOSE 80
CMD ["npx", "serve", "-s", "build"]
```

Build Docker-file
```
sudo docker build -t react-frontend
```

## 6. Build a Backend Container:

* In the backend directory, make a Dockerfile.

* Install Poetry and its dependencies, use the official Python image
* copy the application code, configure environment variables, and open port 8000.
* Make a docker-ignore file to remove files from the image that aren't needed.
```
cd backend
```
```
nano dockerfile
```

Then paste this
```
Use the official Python image with the desired version
FROM python:3.10-slim

Set the working directory in the container
WORKDIR /app

Install Poetry
RUN apt-get update && \
    apt-get install -y curl && \
    curl -sSL https://install.python-poetry.org | python3 - && \
    apt-get clean

Add Poetry to PATH
ENV PATH="/root/.local/bin:$PATH"

Copy pyproject.toml and poetry.lock to the container
COPY pyproject.toml poetry.lock* /app/

Install dependencies using Poetry
RUN poetry install --no-root --no-interaction --no-ansi

# Copy the rest of the application code to the container
COPY . /app

# Ensure the application directory is in the Python path
ENV PYTHONPATH=/app

# Run the database prestart script
RUN poetry run bash ./prestart.sh

# Expose the application port
EXPOSE 8000

# Command to run the backend server
CMD ["poetry", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

Create docker ignore file
```
nano docker ignore
```
then paste this

```
pycache
*.pyc
*.pyo
.mypy_cache
.pytest_cache
.venv
.env
.git
```

* Use docker build to create the Docker image.
```
sudo docker build -t
```

## 7. Make a file called docker-compose.yml using Docker Compose:

```
Nano docker-compose.yml
```
then paste this
```
version: '3.8'

services:
  traefik:
    image: traefik:v2.9
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"    # Port 80 for web traffic
      - "8080:8080" # Traefik dashboard port
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock" # Docker socket for Traefik to detect services
    networks:
      - web

  db:
    image: postgres:latest
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydatabase
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - web

  backend:
    build:
      context: ./backend
    labels:
      - "traefik.http.routers.backend.rule=PathPrefix(/api)" # Proxy requests with /api to backend
      - "traefik.http.services.backend.loadbalancer.server.port=8000"
    environment:
      DATABASE_URL: "postgresql://user:password@db:5432/mydatabase"
    networks:
      - web

  frontend:
    build:
      context: ./frontend
    labels:
      - "traefik.http.routers.frontend.rule=Host(yourdomain.com)" # Main domain goes to frontend
      - "traefik.http.services.frontend.loadbalancer.server.port=80"
    networks:
      - web

volumes:
  pgdata:

networks:
  web:
```
This is an explanation of everything specified in the file above...
   * Specify the frontend, backend, database, and Traefik services.
   * For routing requests, use Traefik as a reverse proxy with labels.
   * Set environment variables to configure the database service's connection details.
   * Configure the database URL's environment variables in the backend service.
   * To define paths for producing frontend and backend images, use the build context.
   * Establish networks for service-to-service communication.

## 8. Configure Your Domain Name:

   * Make a subdomain using a free DNS provider like, (www.freedns.afraid) Set the subdomain's address to the public IP ipv4 address of your instance.
   * For the frontend service, update the docker-compose.yml file with your domain name.
 

## 9. Launch the program

   * To create and start all services in detached mode, run 
```
docker-compose up -d --build
```

* set-up the nginx as proxy manager

## Advantages of Docker Compose usage:

* Simplified Multi-Container Management
* Utilizes a single configuration file to define and operate all services.
* Scalability
* Adding or removing containers as needed is simple.
* Reproducibility
* Guarantees uniform environments for testing, production, and development.

With the help of a reverse proxy and Docker containers, this method offers a strong basis for launching web applications with improved security and speed.