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β
| Service | Port | Purpose |
|---|---|---|
| PostgreSQL | 5432 | Database |
| NestJS API | 3001 | Backend API |
| Next.js Web | 3000 | Frontend 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
.envfiles with secrets - Use minimal base images (Alpine)
- Scan images for vulnerabilities:
docker scan
Performanceβ
- Use
.dockerignoreto 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β
- Learn about Liquibase for database migrations
- Set up observability stack
- Deploy with Kubernetes or Docker Swarm