Skip to main content

Docker & Docker Compose

Overview​

The application uses Docker and Docker Compose to containerize all services, enabling consistent development and deployment environments across different machines.

What is Docker?​

Docker is a containerization platform that packages your application with all its dependencies (libraries, runtime, configuration) into a self-contained unit called a container.

Benefits​

  • Consistency: "Works on my machine" problem is solved
  • Isolation: Each service runs independently without conflicts
  • Scalability: Easy to run multiple instances
  • Portability: Same container runs on any system with Docker installed

What is Docker Compose?​

Docker Compose is a tool for defining and running multi-container Docker applications using a YAML configuration file (docker-compose.yaml).

App Docker Setup​

Main Stack Components​

The main docker-compose.yaml includes:

services:
postgres:
image: postgres:15-alpine
ports:
- '5432:5432'
environment:
- POSTGRES_DB=qms
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- postgres_data:/var/lib/postgresql/data

api:
build:
context: ./apps/api
dockerfile: Dockerfile
ports:
- '3001:3001'
depends_on:
- postgres
environment:
- DB_HOST=postgres
- DB_PORT=5432
- JWT_ACCESS_SECRET=${JWT_ACCESS_SECRET}

web:
build:
context: ./apps/web
dockerfile: Dockerfile
ports:
- '3000:3000'
depends_on:
- api
environment:
- NEXT_PUBLIC_API_BASE_URL=http://localhost:3001

Service Breakdown​

ServicePortPurpose
PostgreSQL5432Database
NestJS API3001Backend API
Next.js Web3000Frontend application

Running with Docker Compose​

Start All Services​

# Start in detached mode (runs in background)
docker-compose up -d

# Start in foreground (see logs)
docker-compose up

# Start specific service
docker-compose up -d postgres

Stop Services​

# Stop all services (containers still exist)
docker-compose stop

# Stop and remove containers
docker-compose down

# Remove containers and volumes (WARNING: deletes data)
docker-compose down -v

View Logs​

# All services
docker-compose logs

# Specific service
docker-compose logs api

# Follow logs (live stream)
docker-compose logs -f api

# Last 100 lines
docker-compose logs --tail=100 api

Interact with Containers​

# Execute command in running container
docker-compose exec postgres psql -U postgres -d qms

# Get shell access
docker-compose exec api /bin/bash

# Check running services
docker-compose ps

Dockerfiles​

PostgreSQL Dockerfile​

Located in infra/db/Dockerfile:

FROM postgres:15-alpine

# Copy initialization scripts
COPY changelogs/ /docker-entrypoint-initdb.d/

# Install Liquibase
RUN apk add --no-cache openjdk11-jre liquibase

NestJS API Dockerfile​

Located in apps/api/Dockerfile:

FROM node:20-alpine

WORKDIR /app

# Copy package files
COPY package.json pnpm-lock.yaml ./

# Install dependencies
RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile

# Copy source code
COPY . .

# Build application
RUN pnpm build

# Expose port
EXPOSE 3001

# Start application
CMD ["node", "dist/main.js"]

Next.js Web Dockerfile​

Located in apps/web/Dockerfile:

FROM node:20-alpine

WORKDIR /app

# Copy package files
COPY package.json pnpm-lock.yaml ./

# Install dependencies
RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile

# Copy source and build
COPY . .
RUN pnpm build

# Expose port
EXPOSE 3000

# Start application
CMD ["pnpm", "start"]

Environment Variables​

Create a .env file in the project root:

# Database
DB_HOST=postgres
DB_PORT=5432
DB_USER=postgres
DB_PASS=postgres
DB_NAME=qms

# API
INTERNAL_API_BASE_URL=http://localhost:3001
JWT_ACCESS_SECRET=your-secret-key

# Frontend
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001
FRONTEND_URL=http://localhost:3000

Docker Networking​

Docker Compose creates a virtual network that allows containers to communicate by service name:

Client β†’ web:3000 β†’ api:3001 β†’ postgres:5432
(localhost) (api service name) (postgres service name)

Services can reference each other by name (e.g., postgres instead of localhost).

Volumes​

Volumes persist data between container restarts:

volumes:
postgres_data:
driver: local

Volume Management​

# List volumes
docker volume ls

# Inspect volume
docker volume inspect qms_postgres_data

# Remove unused volumes
docker volume prune

# Remove specific volume
docker volume rm qms_postgres_data

Useful Commands​

Container Management​

# View all containers (running and stopped)
docker ps -a

# Get container IP address
docker inspect qms-api-1 | grep "IPAddress"

# Copy files from container
docker cp qms-postgres-1:/var/lib/postgresql/data ./backup

# View resource usage
docker stats

Debugging​

# View container logs with timestamps
docker-compose logs --timestamps api

# Search logs
docker-compose logs api | grep ERROR

# Rebuild images
docker-compose build --no-cache

# Remove all containers and images
docker system prune -a

Common Issues​

Port Already in Use​

# Find process using port 3001
lsof -i :3001

# Kill process
kill -9 <PID>

# Or use Docker Compose with different port
docker-compose -f docker-compose.yaml up -d \
-e "API_PORT=3002"

Database Connection Refused​

# Wait for postgres to be ready
docker-compose exec api npm run prisma:migrate

# Check postgres logs
docker-compose logs postgres

# Verify postgres is running
docker-compose ps postgres

Out of Disk Space​

# Clean up unused container data
docker system prune

# Remove all, including images
docker system prune -a --volumes

Production Considerations​

Security​

  • Use secrets management (Docker Secrets or Kubernetes Secrets)
  • Never commit .env files with secrets
  • Use minimal base images (Alpine)
  • Scan images for vulnerabilities: docker scan

Performance​

  • Use .dockerignore to exclude unnecessary files
  • Minimize layer count in Dockerfiles
  • Use multi-stage builds for smaller images
  • Set resource limits: memory: 512M

Logging​

logging:
driver: json-file
options:
max-size: '10m'
max-file: '3'

Next Steps​