Skip to main content

Overview

The E-commerce API is containerized using Docker with a multi-stage build process. This guide covers deploying the complete stack using Docker Compose.

Architecture

The application consists of three services:
  • MySQL: Database server (MySQL 8.4)
  • Backend: Node.js API server (TypeScript)
  • Frontend: Next.js application

Prerequisites

  • Docker Engine 20.10+
  • Docker Compose 2.0+
  • Environment variables configured (see Environment Setup)

Docker Compose Configuration

The complete stack is defined in docker-compose.yml:1-44:
services:
  mysql:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    ports:
      - "3306:3306"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  backend:
    build: ./backend
    restart: unless-stopped
    env_file:
      - ./backend/.env
    depends_on:
      mysql:
        condition: service_healthy
    ports:
      - "3000:3000"

  frontend:
    restart: unless-stopped
    build:
      context: ./frontend
      args:
        NEXT_PUBLIC_API_URL: http://backend:3000
    ports:
      - "4200:3000"
    depends_on:
      - backend

volumes:
  db_data:

Backend Dockerfile

The backend uses a multi-stage build for optimized production images (backend/dockerfile:1-32):
FROM node:20-slim AS base

# Install dependencies (including dev for compiling TS)
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Compile TypeScript
FROM deps AS builder
WORKDIR /app
COPY . .
RUN npx prisma generate
RUN npm run build

# Production image (only production dependencies)
FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production

COPY package*.json ./
RUN npm ci --omit=dev

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder /app/prisma ./prisma

EXPOSE 3000

CMD ["node", "dist/server.js"]

Build Stages Explained

1

Base Stage

Uses node:20-slim as the foundation for all subsequent stages
2

Dependencies Stage

Installs all dependencies (including devDependencies) needed for building
3

Builder Stage

  • Generates Prisma Client
  • Compiles TypeScript to JavaScript (outputs to dist/)
4

Runner Stage (Final)

  • Installs only production dependencies (--omit=dev)
  • Copies compiled code and Prisma client from builder
  • Sets NODE_ENV=production
  • Exposes port 3000

Deployment Steps

1

Prepare environment files

Create .env in the project root for Docker Compose:
MYSQL_ROOT_PASSWORD="rootpassword"
MYSQL_DATABASE="ecommerce_db"
MYSQL_USER="ecommerce_user"
MYSQL_PASSWORD="ecommerce_password"
Create backend/.env for the API:
DATABASE_URL="mysql://ecommerce_user:ecommerce_password@mysql:3306/ecommerce_db"
JWT_SECRET="your-secret-key"
CLOUDINARY_CLOUD_NAME="your-cloud-name"
CLOUDINARY_API_KEY="your-api-key"
CLOUDINARY_API_SECRET="your-api-secret"
The DATABASE_URL host must be mysql (the service name), not localhost.
2

Build and start services

docker-compose up --build -d
This will:
  • Build the backend and frontend images
  • Pull the MySQL 8.4 image
  • Create a named volume for database persistence
  • Start all services in detached mode
3

Wait for health checks

The backend service depends on MySQL being healthy:
docker-compose ps
Wait until all services show as “Up” or “healthy”
4

Run database migrations

Execute Prisma migrations inside the backend container:
docker-compose exec backend npx prisma migrate deploy
Use migrate deploy in production, not migrate dev. See Database Migrations for details.
5

(Optional) Seed the database

Populate with initial data:
docker-compose exec backend npm run prisma:seed
6

Verify deployment

Test the API:
curl http://localhost:3000/products
Access the frontend at http://localhost:4200

Service Dependencies

The compose file ensures proper startup order:
backend:
  depends_on:
    mysql:
      condition: service_healthy  # Waits for MySQL health check

frontend:
  depends_on:
    - backend  # Waits for backend to start (not healthy)

Health Check Configuration

MySQL service includes a health check:
  • Test: mysqladmin ping -h localhost
  • Interval: 10 seconds
  • Timeout: 5 seconds
  • Retries: 5 attempts
  • Start period: 30 seconds (grace period)
The backend won’t start until MySQL passes this health check.

Volume Persistence

Database data is persisted in a named volume:
volumes:
  db_data:
This ensures data survives container restarts. To completely reset the database:
docker-compose down -v  # WARNING: Deletes all data

Useful Commands

View Logs

# All services
docker-compose logs -f

# Specific service
docker-compose logs -f backend

Restart Services

# Restart all
docker-compose restart

# Restart one service
docker-compose restart backend

Execute Commands

# Open shell in backend container
docker-compose exec backend sh

# Run Prisma commands
docker-compose exec backend npx prisma studio

Stop and Remove

# Stop services (keeps volumes)
docker-compose down

# Stop and remove volumes (deletes data)
docker-compose down -v

Production Considerations

The example configuration is for development. For production:
  • Use secrets management (Docker Secrets, Vault, etc.) instead of .env files
  • Configure reverse proxy (nginx, Traefik) for SSL termination
  • Set resource limits (memory, CPU) for containers
  • Use managed database service instead of container
  • Enable Docker logging driver (e.g., json-file with rotation)
  • Implement monitoring and alerting

Troubleshooting

Backend fails to connect to MySQL

Symptom: Error: P1001: Can't reach database server at mysql:3306 Solution: Ensure the DATABASE_URL uses mysql as hostname, not localhost:
DATABASE_URL="mysql://user:password@mysql:3306/db"

Prisma Client not generated

Symptom: Error: Cannot find module '@prisma/client' Solution: Rebuild the backend image:
docker-compose build --no-cache backend
docker-compose up -d backend

Port already in use

Symptom: Error: bind: address already in use Solution: Change the port mapping in docker-compose.yml:
ports:
  - "3001:3000"  # Map to different host port

Next Steps

Database Migrations

Learn how to manage schema changes with Prisma

Image Uploads

Configure Cloudinary for product images

Build docs developers (and LLMs) love