Skip to main content

Overview

Crafter LoL uses multi-stage Docker builds for efficient containerization of both backend and frontend.
Multi-stage builds reduce final image size by separating build dependencies from runtime dependencies.

Backend Dockerfile

Located at source/Back/crafter/Dockerfile:
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B

FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 5000
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]

Stage 1: Build

FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
  • Maven 3.9.6: Build tool
  • Eclipse Temurin 17: OpenJDK distribution
  • Size: ~700MB (discarded after build)
COPY pom.xml .
RUN mvn dependency:go-offline -B
Why? Docker caches layers. By copying pom.xml first and downloading dependencies before copying source code, dependencies are only re-downloaded when pom.xml changes.Benefits:
  • Faster builds (deps cached)
  • Reduced bandwidth usage
RUN mvn package -DskipTests -B
Flags:
  • -DskipTests: Skip test execution (compile but don’t run)
  • -B: Batch mode (non-interactive, no download progress)
Note: Run tests in CI/CD pipeline before building image.

Stage 2: Runtime

FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 5000
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
  • JRE only (no JDK/Maven): Smaller image
  • Alpine Linux: Minimal Linux distribution (~5MB)
  • Final image size: ~200MB (vs 700MB with full JDK)
-XX:+UseContainerSupport
Enables JVM container awareness. Without this, JVM may allocate memory based on host machine, not container limits.
-XX:MaxRAMPercentage=75.0
Limits JVM heap to 75% of container memory. Leaves 25% for:
  • JVM metaspace
  • Native memory
  • OS overhead
Example: 512MB container → 384MB heap, 128MB other
ENTRYPOINT makes the container behave like an executable:
docker run my-app --spring.profiles.active=prod
Arguments are appended to ENTRYPOINT. With CMD, arguments would replace the entire command.

Building Backend Image

1

Navigate to Backend Directory

cd source/Back/crafter
2

Build Docker Image

docker build -t crafter-lol-backend:latest .
First build takes 2-3 minutes. Subsequent builds with cached dependencies: ~30 seconds.
3

Verify Image

docker images | grep crafter-lol-backend
Expected output:
crafter-lol-backend   latest   abc123def456   2 minutes ago   200MB

Running Backend Container

Basic Run

docker run -p 5000:5000 crafter-lol-backend:latest
-p 5000:5000
string
Port mapping: host:container
  • Host port 5000 maps to container port 5000
  • Access at http://localhost:5000

With Environment Variables

docker run -p 5000:5000 \
  -e PORT=5000 \
  -e CORS_ORIGINS=http://localhost:5173 \
  -e ddragon.version=16.3.1 \
  crafter-lol-backend:latest

Production Run with Resource Limits

docker run -d \
  --name crafter-backend \
  -p 5000:5000 \
  --memory="512m" \
  --cpus="1.0" \
  --restart unless-stopped \
  -e PORT=5000 \
  -e CORS_ORIGINS=https://yourdomain.com \
  crafter-lol-backend:latest
Runs container in background. View logs with:
docker logs -f crafter-backend
Resource limits prevent container from consuming all host resources:
  • --memory="512m": Maximum 512MB RAM
  • --cpus="1.0": Maximum 1 CPU core
With MaxRAMPercentage=75.0, JVM heap will be ~384MB.
Restart policy:
  • Container restarts on failure
  • Does NOT restart if manually stopped
  • Survives host reboots

Frontend Dockerfile

Create source/Front/Crafter_League_of_Legends/Dockerfile:
# Stage 1: Build
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production=false
COPY . .
RUN npm run build

# Stage 2: Serve with Nginx
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Nginx Configuration

Create source/Front/Crafter_League_of_Legends/nginx.conf:
server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    # Gzip compression
    gzip on;
    gzip_types text/css application/javascript application/json image/svg+xml;
    gzip_min_length 1000;

    # SPA routing: serve index.html for all routes
    location / {
        try_files $uri $uri/ /index.html;
    }

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

Building Frontend Image

1

Navigate to Frontend Directory

cd source/Front/Crafter_League_of_Legends
2

Build with Environment Variables

docker build \
  --build-arg VITE_API_URL=http://localhost:5000/api \
  -t crafter-lol-frontend:latest .
Vite environment variables must be provided at build time, not runtime.
3

Run Frontend Container

docker run -p 80:80 crafter-lol-frontend:latest
Access at http://localhost

Docker Compose

Create docker-compose.yml in project root:
version: '3.8'

services:
  backend:
    build:
      context: ./source/Back/crafter
      dockerfile: Dockerfile
    container_name: crafter-backend
    ports:
      - "5000:5000"
    environment:
      - PORT=5000
      - CORS_ORIGINS=http://localhost:5173
      - ddragon.version=16.3.1
      - ddragon.language=es_MX
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/api/game/question"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  frontend:
    build:
      context: ./source/Front/Crafter_League_of_Legends
      dockerfile: Dockerfile
      args:
        - VITE_API_URL=http://localhost:5000/api
    container_name: crafter-frontend
    ports:
      - "5173:80"
    depends_on:
      - backend
    restart: unless-stopped

Usage

docker-compose up -d

Health Checks

Health check configuration ensures container is actually serving traffic:
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:5000/api/game/question"]
  interval: 30s      # Check every 30 seconds
  timeout: 10s       # Max 10 seconds per check
  retries: 3         # 3 failures = unhealthy
  start_period: 40s  # Grace period on startup
Check health status:
docker ps
Look for (healthy) in the STATUS column.

Production Considerations

Bad:
FROM eclipse-temurin:17-jre-alpine
Good:
FROM eclipse-temurin:17.0.10_7-jre-alpine
Ensures reproducible builds.
Add security by running as non-root:
FROM eclipse-temurin:17-jre-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
COPY --from=build /app/target/*.jar app.jar
ENTRYPOINT ["java", ...]
Build for ARM and x86:
docker buildx build --platform linux/amd64,linux/arm64 \
  -t crafter-lol-backend:latest .
Never hardcode secrets in Dockerfile. Use:
  • Environment variables
  • Docker secrets
  • External secret management (Vault, AWS Secrets Manager)

Debugging Containers

docker logs -f crafter-backend

Next Steps

Production Deployment

Deploy to cloud platforms

Environment Configuration

Configure for different environments

Build docs developers (and LLMs) love