Skip to main content
This guide covers containerizing and deploying Sistema de Ventas using Docker and Docker Compose.

Architecture Overview

Docker deployment includes:
  • 2 Database containers: MySQL 8.0, PostgreSQL 15
  • 11 Spring Boot services: Config, Registry, Gateway + 8 microservices
  • 1 Angular frontend: Nginx-served static files
  • Docker network: Internal service communication

Prerequisites

Ensure Docker and Docker Compose are installed:
  • Docker Engine 20.10+
  • Docker Compose 2.0+
  • 8GB RAM minimum
  • 20GB free disk space

Verify Docker Installation

docker --version
docker-compose --version

Project Structure

Sistema-ventas-ms/
├── docker-compose.yml
├── .env
├── jea-config-server/
│   └── Dockerfile
├── jea-registry-server/
│   └── Dockerfile
├── jea-gateway-server/
│   └── Dockerfile
├── jea-auth/
│   └── Dockerfile
├── jea-catalogo/
│   └── Dockerfile
├── jea-cliente/
│   └── Dockerfile
├── jea-venta/
│   └── Dockerfile
├── jea-compra/
│   └── Dockerfile
├── jea-pedidos/
│   └── Dockerfile
├── jea-pagos/
│   └── Dockerfile
├── jea-proveedor/
│   └── Dockerfile
├── sistema-ventas-frontend/
│   └── Dockerfile
└── sql-scripts/
    ├── 01-init-databases.sql
    └── 02-datos-ejemplo.sql

Create Dockerfiles

Step 1: Backend Service Dockerfile Template

Create Dockerfile in each Spring Boot service directory:
Dockerfile
# Multi-stage build for smaller image size
FROM maven:3.9-eclipse-temurin-17-alpine AS build
WORKDIR /app

# Copy pom.xml and download dependencies (cached layer)
COPY pom.xml .
RUN mvn dependency:go-offline -B

# Copy source code and build
COPY src ./src
RUN mvn clean package -DskipTests

# Runtime stage
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app

# Create non-root user
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring

# Copy JAR from build stage
COPY --from=build /app/target/*.jar app.jar

# Health check
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget --quiet --tries=1 --spider http://localhost:8080/actuator/health || exit 1

# Run application
ENTRYPOINT ["java", "-jar", "app.jar"]
Use for: Gateway, Cliente, Catalogo, Venta, Compra, Pedidos, Pagos, Proveedor

Step 2: Frontend Dockerfile

sistema-ventas-frontend/Dockerfile
# Build stage
FROM node:18-alpine AS build
WORKDIR /app

# Copy package files
COPY package*.json ./
RUN npm ci --legacy-peer-deps

# Copy source and build
COPY . .
RUN npm run build -- --configuration production

# Production stage
FROM nginx:1.25-alpine

# Copy build output
COPY --from=build /app/dist/sistema-ventas-frontend/browser /usr/share/nginx/html

# Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Expose port
EXPOSE 80

# Health check
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget --quiet --tries=1 --spider http://localhost:80 || exit 1

CMD ["nginx", "-g", "daemon off;"]

Step 3: Nginx Configuration for Frontend

sistema-ventas-frontend/nginx.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    # Enable gzip compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;

    # Angular routing
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Cache static assets
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
}

Docker Compose Configuration

Step 4: Create docker-compose.yml

docker-compose.yml
version: '3.8'

services:
  # ==========================================
  # DATABASES
  # ==========================================
  
  mysql:
    image: mysql:8.0
    container_name: mysql-ventas
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123}
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
      - ./sql-scripts/01-init-databases.sql:/docker-entrypoint-initdb.d/01-init.sql
      - ./sql-scripts/02-datos-ejemplo.sql:/docker-entrypoint-initdb.d/02-datos.sql
    networks:
      - ventas-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  postgres:
    image: postgres:15-alpine
    container_name: postgres-ventas
    restart: unless-stopped
    environment:
      POSTGRES_DB: cliente-jea
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-123456}
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - ventas-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ==========================================
  # INFRASTRUCTURE SERVICES
  # ==========================================

  config-server:
    build:
      context: ./jea-config-server
      dockerfile: Dockerfile
    container_name: config-server
    restart: unless-stopped
    ports:
      - "7070:7070"
    environment:
      SPRING_PROFILES_ACTIVE: docker
    networks:
      - ventas-network
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:7070/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 5

  registry-server:
    build:
      context: ./jea-registry-server
      dockerfile: Dockerfile
    container_name: registry-server
    restart: unless-stopped
    depends_on:
      config-server:
        condition: service_healthy
    ports:
      - "8090:8090"
    environment:
      SPRING_PROFILES_ACTIVE: docker
      CONFIG_SERVER_URI: http://config-server:7070
    networks:
      - ventas-network
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8090/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 5

  gateway-server:
    build:
      context: ./jea-gateway-server
      dockerfile: Dockerfile
    container_name: gateway-server
    restart: unless-stopped
    depends_on:
      config-server:
        condition: service_healthy
      registry-server:
        condition: service_healthy
    ports:
      - "8085:8085"
    environment:
      SPRING_PROFILES_ACTIVE: docker
      CONFIG_SERVER_URI: http://config-server:7070
      EUREKA_URI: http://registry-server:8090/eureka
    networks:
      - ventas-network
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8085/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 5

  # ==========================================
  # BUSINESS MICROSERVICES
  # ==========================================

  auth-service:
    build:
      context: ./jea-auth
      dockerfile: Dockerfile
    container_name: auth-service
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
      config-server:
        condition: service_healthy
      registry-server:
        condition: service_healthy
    environment:
      SPRING_PROFILES_ACTIVE: docker
      CONFIG_SERVER_URI: http://config-server:7070
      EUREKA_URI: http://registry-server:8090/eureka
      DB_HOST: mysql
      DB_PORT: 3306
      DB_NAME: jeaauth
      DB_USER: root
      DB_PASSWORD: ""
    networks:
      - ventas-network

  catalogo-service:
    build:
      context: ./jea-catalogo
      dockerfile: Dockerfile
    container_name: catalogo-service
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
      registry-server:
        condition: service_healthy
    environment:
      SPRING_PROFILES_ACTIVE: docker
      CONFIG_SERVER_URI: http://config-server:7070
      EUREKA_URI: http://registry-server:8090/eureka
      DB_HOST: mysql
      DB_NAME: jeacatalogo
    networks:
      - ventas-network

  cliente-service:
    build:
      context: ./jea-cliente
      dockerfile: Dockerfile
    container_name: cliente-service
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
      registry-server:
        condition: service_healthy
    environment:
      SPRING_PROFILES_ACTIVE: docker
      CONFIG_SERVER_URI: http://config-server:7070
      EUREKA_URI: http://registry-server:8090/eureka
      DB_HOST: postgres
      DB_PORT: 5432
      DB_NAME: cliente-jea
      DB_USER: postgres
      DB_PASSWORD: ${POSTGRES_PASSWORD:-123456}
    networks:
      - ventas-network

  venta-service:
    build:
      context: ./jea-venta
      dockerfile: Dockerfile
    container_name: venta-service
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
      registry-server:
        condition: service_healthy
    environment:
      SPRING_PROFILES_ACTIVE: docker
      CONFIG_SERVER_URI: http://config-server:7070
      EUREKA_URI: http://registry-server:8090/eureka
      DB_HOST: mysql
      DB_NAME: jeaventa
    networks:
      - ventas-network

  compra-service:
    build:
      context: ./jea-compra
      dockerfile: Dockerfile
    container_name: compra-service
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
      registry-server:
        condition: service_healthy
    environment:
      SPRING_PROFILES_ACTIVE: docker
      CONFIG_SERVER_URI: http://config-server:7070
      EUREKA_URI: http://registry-server:8090/eureka
      DB_HOST: mysql
      DB_NAME: jeacompra
    networks:
      - ventas-network

  pedidos-service:
    build:
      context: ./jea-pedidos
      dockerfile: Dockerfile
    container_name: pedidos-service
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
      registry-server:
        condition: service_healthy
    environment:
      SPRING_PROFILES_ACTIVE: docker
      CONFIG_SERVER_URI: http://config-server:7070
      EUREKA_URI: http://registry-server:8090/eureka
      DB_HOST: mysql
      DB_NAME: jeapedidos
    networks:
      - ventas-network

  pagos-service:
    build:
      context: ./jea-pagos
      dockerfile: Dockerfile
    container_name: pagos-service
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
      registry-server:
        condition: service_healthy
    environment:
      SPRING_PROFILES_ACTIVE: docker
      CONFIG_SERVER_URI: http://config-server:7070
      EUREKA_URI: http://registry-server:8090/eureka
      DB_HOST: mysql
      DB_NAME: jeapagos
    networks:
      - ventas-network

  proveedor-service:
    build:
      context: ./jea-proveedor
      dockerfile: Dockerfile
    container_name: proveedor-service
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
      registry-server:
        condition: service_healthy
    environment:
      SPRING_PROFILES_ACTIVE: docker
      CONFIG_SERVER_URI: http://config-server:7070
      EUREKA_URI: http://registry-server:8090/eureka
      DB_HOST: mysql
      DB_NAME: jeaproveedor
    networks:
      - ventas-network

  # ==========================================
  # FRONTEND
  # ==========================================

  frontend:
    build:
      context: ./sistema-ventas-frontend
      dockerfile: Dockerfile
    container_name: frontend-ventas
    restart: unless-stopped
    depends_on:
      gateway-server:
        condition: service_healthy
    ports:
      - "80:80"
    environment:
      API_URL: http://gateway-server:8085
    networks:
      - ventas-network

# ==========================================
# NETWORKS & VOLUMES
# ==========================================

networks:
  ventas-network:
    driver: bridge

volumes:
  mysql-data:
    driver: local
  postgres-data:
    driver: local

Step 5: Environment Variables File

.env
# Database Credentials
MYSQL_ROOT_PASSWORD=root123
POSTGRES_PASSWORD=123456

# Spring Profiles
SPRING_PROFILES_ACTIVE=docker

# Config Server
CONFIG_SERVER_URI=http://config-server:7070
CONFIG_SERVER_USER=root
CONFIG_SERVER_PASSWORD=123456

# Eureka
EUREKA_URI=http://registry-server:8090/eureka

# JWT Secret
JWT_SECRET=your-production-secret-key-change-this

# Application URLs
FRONTEND_URL=http://localhost
BACKEND_URL=http://localhost:8085

Docker Profile Configuration

Create Docker-specific configuration in config-data/ directory:
config-data/application-docker.yml
spring:
  cloud:
    config:
      uri: ${CONFIG_SERVER_URI:http://config-server:7070}
  datasource:
    url: jdbc:mysql://${DB_HOST:mysql}:${DB_PORT:3306}/${DB_NAME}
    username: ${DB_USER:root}
    password: ${DB_PASSWORD:}
    
eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_URI:http://registry-server:8090/eureka}
  instance:
    preferIpAddress: true
    instance-id: ${spring.application.name}:${random.value}

Deployment Steps

Step 6: Build and Start Services

1

Navigate to Project Root

cd Sistema-ventas-ms
2

Build All Images

# Build all services (takes 10-15 minutes first time)
docker-compose build

# Build specific service
docker-compose build auth-service
3

Start Infrastructure Services First

# Start databases
docker-compose up -d mysql postgres

# Wait for databases to be healthy (30 seconds)
docker-compose ps

# Start config server
docker-compose up -d config-server

# Wait 30 seconds
sleep 30

# Start registry
docker-compose up -d registry-server

# Wait 30 seconds
sleep 30
4

Start All Services

# Start everything
docker-compose up -d

# View logs
docker-compose logs -f

# View specific service logs
docker-compose logs -f gateway-server
5

Verify Deployment

# Check all containers are running
docker-compose ps

# Check Eureka Dashboard
open http://localhost:8090

# Check Frontend
open http://localhost

Management Commands

View Status

# List all containers
docker-compose ps

# Check service health
docker-compose ps | grep "healthy"

# View resource usage
docker stats

View Logs

docker-compose logs -f

Restart Services

# Restart single service
docker-compose restart auth-service

# Restart all services
docker-compose restart

# Restart infrastructure services
docker-compose restart config-server registry-server gateway-server

Stop and Remove

# Stop all services
docker-compose stop

# Stop and remove containers
docker-compose down

# Remove containers, networks, and volumes
docker-compose down -v

# Remove containers and images
docker-compose down --rmi all

Scaling Services

Scale business services for load balancing:
# Scale venta service to 3 instances
docker-compose up -d --scale venta-service=3

# Scale multiple services
docker-compose up -d --scale venta-service=3 --scale compra-service=2

# View scaled instances
docker-compose ps venta-service

Production Deployment

Step 7: Production docker-compose Override

docker-compose.prod.yml
version: '3.8'

services:
  config-server:
    environment:
      SPRING_PROFILES_ACTIVE: prod
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M

  registry-server:
    environment:
      SPRING_PROFILES_ACTIVE: prod
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M

  gateway-server:
    environment:
      SPRING_PROFILES_ACTIVE: prod
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 1G

  mysql:
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G

  frontend:
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
Deploy with production config:
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Backup and Restore

Database Backup

# Backup MySQL databases
docker exec mysql-ventas mysqldump -u root --all-databases > backup-mysql-$(date +%Y%m%d).sql

# Backup PostgreSQL
docker exec postgres-ventas pg_dump -U postgres cliente-jea > backup-postgres-$(date +%Y%m%d).sql

# Backup with docker-compose
docker-compose exec mysql mysqldump -u root --all-databases > backup.sql

Database Restore

# Restore MySQL
docker exec -i mysql-ventas mysql -u root < backup-mysql-20240101.sql

# Restore PostgreSQL
docker exec -i postgres-ventas psql -U postgres cliente-jea < backup-postgres-20240101.sql

Monitoring

Container Health

# Check container health status
docker inspect --format='{{.State.Health.Status}}' config-server

# View health check logs
docker inspect --format='{{json .State.Health}}' gateway-server | jq

Resource Monitoring

# Real-time stats
docker stats

# Export stats to file
docker stats --no-stream > container-stats.txt

# Monitor specific containers
docker stats mysql-ventas postgres-ventas gateway-server

Troubleshooting

Issue: Service container exits immediatelySolutions:
# Check logs
docker-compose logs service-name

# Check if port is already in use
lsof -i :8085

# Restart with fresh state
docker-compose down -v
docker-compose up -d
Issue: Services can’t connect to MySQL/PostgreSQLSolutions:
  1. Verify databases are healthy:
    docker-compose ps mysql postgres
    
  2. Check database logs:
    docker-compose logs mysql
    
  3. Wait for database initialization (can take 60 seconds)
  4. Verify environment variables in docker-compose.yml
Issue: Microservices don’t appear in Eureka dashboardSolutions:
  1. Check registry-server is healthy
  2. Verify EUREKA_URI environment variable
  3. Check service logs for connection errors
  4. Wait 60 seconds for registration
  5. Restart service: docker-compose restart service-name
Issue: java.lang.OutOfMemoryError in logsSolutions:
# Add memory limits to service in docker-compose.yml
services:
  venta-service:
    environment:
      JAVA_OPTS: "-Xmx512m -Xms256m"
    deploy:
      resources:
        limits:
          memory: 1G
Issue: Maven build fails during docker buildSolutions:
# Clear Docker build cache
docker-compose build --no-cache

# Build with more memory
docker-compose build --build-arg MAVEN_OPTS="-Xmx1024m"

# Check Maven repository access
docker-compose run --rm service-name mvn dependency:resolve

Docker Compose Useful Commands

# Start all services
docker-compose up -d

# Start specific services
docker-compose up -d mysql config-server

CI/CD Integration

GitHub Actions Example

.github/workflows/docker-deploy.yml
name: Docker Deploy

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Login to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Build and Push Images
        run: |
          docker-compose build
          docker-compose push
      
      - name: Deploy to Server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /app/sistema-ventas
            docker-compose pull
            docker-compose up -d

Best Practices

  • Use multi-stage builds to reduce image size
  • Implement health checks for all services
  • Use Docker volumes for persistent data
  • Set resource limits in production
  • Use Docker secrets for sensitive data
  • Enable logging drivers for centralized logs
  • Implement backup strategy for databases
  • Use Docker networks for service isolation
  • Tag images with version numbers
  • Monitor container resource usage

Next Steps

Monitoring & Logging

Set up monitoring with Prometheus and Grafana

API Documentation

Explore API endpoints and integrations

Build docs developers (and LLMs) love