Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 49 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,53 @@
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN apk add --no-cache maven && mvn clean package -DskipTests
# ============================================================
# Stage 1: Build
# ============================================================
FROM eclipse-temurin:21-jdk-alpine AS build

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# Install Maven
RUN apk add --no-cache maven

WORKDIR /workspace

# Copy POM first to cache dependency layer
COPY backend/pom.xml .

# Download dependencies (cached unless pom.xml changes)
RUN mvn dependency:go-offline -B --no-transfer-progress

# Copy source code and build the fat JAR, skipping tests
COPY backend/src src
RUN mvn package -DskipTests -B --no-transfer-progress

# ============================================================
# Stage 2: Runtime
# ============================================================
FROM eclipse-temurin:21-jre-alpine AS runtime

# Create a non-root user for security
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/target/*.jar app.jar
RUN chown appuser:appgroup app.jar

WORKDIR /app

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

# Ensure the app directory is owned by the non-root user
RUN chown -R appuser:appgroup /app

USER appuser

# Expose application port and management port
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
EXPOSE 8081

# Health check – relies on Spring Actuator management port
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD wget -qO- http://localhost:8081/actuator/health || exit 1

# JVM tuning flags for containers (respects cgroup memory limits)
ENV JAVA_OPTS="-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:+ExitOnOutOfMemoryError \
-Djava.security.egd=file:/dev/./urandom"

ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar app.jar"]
52 changes: 52 additions & 0 deletions Dockerfile.orig
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# ============================================================
# Stage 1: Build
# ============================================================
FROM eclipse-temurin:21-jdk-alpine AS build

# Install Maven
RUN apk add --no-cache maven

WORKDIR /workspace

# Copy POM first to cache dependency layer
COPY backend/pom.xml .

# Download dependencies (cached unless pom.xml changes)
RUN mvn dependency:go-offline -B --no-transfer-progress

# Copy source code and build the fat JAR, skipping tests
COPY backend/src src
RUN mvn package -DskipTests -B --no-transfer-progress

# ============================================================
# Stage 2: Runtime
# ============================================================
FROM eclipse-temurin:21-jre-alpine AS runtime

# Create a non-root user for security
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

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

# Ensure the app directory is owned by the non-root user
RUN chown -R appuser:appgroup /app

USER appuser

# Expose application port
EXPOSE 8080

# Health check – relies on Spring Actuator
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1

# JVM tuning flags for containers (respects cgroup memory limits)
ENV JAVA_OPTS="-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:+ExitOnOutOfMemoryError \
-Djava.security.egd=file:/dev/./urandom"

ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar app.jar"]
52 changes: 52 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
version: "3.9"

# Production overrides – extend the base docker-compose.yml with:
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

services:

# ── Spring Boot application ───────────────────────────────
app:
environment:
SPRING_PROFILES_ACTIVE: prod
SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL}
SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}
SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
JWT_ACCESS_TOKEN_EXPIRATION_MS: ${JWT_ACCESS_TOKEN_EXPIRATION_MS:-900000}
JWT_REFRESH_TOKEN_EXPIRATION_MS: ${JWT_REFRESH_TOKEN_EXPIRATION_MS:-604800000}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS}
JAVA_OPTS: >-
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=75.0
-XX:+ExitOnOutOfMemoryError
-Djava.security.egd=file:/dev/./urandom
restart: always
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M

# ── MariaDB ───────────────────────────────────────────────
mariadb:
environment:
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MARIADB_DATABASE: ${DB_NAME:-jobtracker}
MARIADB_USER: ${SPRING_DATASOURCE_USERNAME}
MARIADB_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
ports: [] # do not expose DB port in production
restart: always

# ── Prometheus ────────────────────────────────────────────
prometheus:
ports: [] # do not expose Prometheus externally in production
restart: always

# ── Grafana ───────────────────────────────────────────────
grafana:
environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD}
GF_SERVER_ROOT_URL: ${GRAFANA_ROOT_URL:-http://localhost:3000}
restart: always
105 changes: 85 additions & 20 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,106 @@
version: '3.8'
version: "3.9"

services:
db:
image: mariadb:11.2
container_name: jobtracker-db

# ── Spring Boot application ───────────────────────────────
app:
build:
context: .
dockerfile: Dockerfile
container_name: job-tracker-app
ports:
- "8080:8080"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: jobtracker
MYSQL_USER: jobtracker
MYSQL_PASSWORD: jobtracker
SPRING_DATASOURCE_URL: jdbc:mariadb://mariadb:3306/jobtracker?createDatabaseIfNotExist=true&characterEncoding=UTF-8&useUnicode=true
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: root
# WARNING: Replace this secret before any production use. This value is for development only.
JWT_SECRET: ChangeThisToAVeryLongSecretKeyForJWTTokensInDevelopment
CORS_ALLOWED_ORIGINS: "http://localhost:3000,http://localhost:5173"
depends_on:
mariadb:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8081/actuator/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
networks:
- job-tracker-net
restart: unless-stopped

# ── MariaDB ───────────────────────────────────────────────
mariadb:
image: mariadb:11
container_name: job-tracker-mariadb
ports:
- "3306:3306"
environment:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: jobtracker
volumes:
- mariadb_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- job-tracker-net
restart: unless-stopped

app:
build: .
container_name: jobtracker-app
# ── Prometheus ────────────────────────────────────────────
prometheus:
image: prom/prometheus:v2.54.1
container_name: job-tracker-prometheus
ports:
- "8080:8080"
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:9090/-/healthy"]
interval: 15s
timeout: 5s
retries: 3
start_period: 15s
networks:
- job-tracker-net
restart: unless-stopped

# ── Grafana ───────────────────────────────────────────────
grafana:
image: grafana/grafana:10.4.8
container_name: job-tracker-grafana
ports:
- "3000:3000"
environment:
DB_URL: jdbc:mariadb://db:3306/jobtracker?createDatabaseIfNotExist=true&characterEncoding=UTF-8&useUnicode=true
DB_USERNAME: jobtracker
DB_PASSWORD: jobtracker
JWT_SECRET: ${JWT_SECRET:-ChangeThisToASecureRandomSecretKeyInProductionAtLeast256BitsLong}
JWT_ACCESS_TOKEN_EXPIRATION_MS: 900000
JWT_REFRESH_TOKEN_EXPIRATION_MS: 604800000
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:3000}
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
- ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
- ./grafana/dashboards:/etc/grafana/dashboards:ro
depends_on:
db:
prometheus:
condition: service_healthy
networks:
Comment thread
vitorhugo-java marked this conversation as resolved.
- job-tracker-net
restart: unless-stopped

volumes:
mariadb_data:
prometheus_data:
grafana_data:

networks:
job-tracker-net:
driver: bridge
99 changes: 99 additions & 0 deletions docker-compose.yml.orig
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
version: "3.9"

services:

# ── Spring Boot application ───────────────────────────────
app:
build:
context: .
dockerfile: Dockerfile
container_name: job-tracker-app
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mariadb://mariadb:3306/jobtracker?createDatabaseIfNotExist=true&characterEncoding=UTF-8&useUnicode=true
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: root
# WARNING: Replace this secret before any production use. This value is for development only.
JWT_SECRET: ChangeThisToAVeryLongSecretKeyForJWTTokensInDevelopment
CORS_ALLOWED_ORIGINS: "http://localhost:3000,http://localhost:5173"
depends_on:
mariadb:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
networks:
- job-tracker-net
restart: unless-stopped

# ── MariaDB ───────────────────────────────────────────────
mariadb:
image: mariadb:11
container_name: job-tracker-mariadb
ports:
- "3306:3306"
environment:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: jobtracker
volumes:
- mariadb_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- job-tracker-net
restart: unless-stopped

# ── Prometheus ────────────────────────────────────────────
prometheus:
image: prom/prometheus:latest
container_name: job-tracker-prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
networks:
- job-tracker-net
restart: unless-stopped

# ── Grafana ───────────────────────────────────────────────
grafana:
image: grafana/grafana:latest
container_name: job-tracker-grafana
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
- ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
- ./grafana/dashboards:/etc/grafana/dashboards:ro
depends_on:
- prometheus
networks:
- job-tracker-net
restart: unless-stopped

volumes:
mariadb_data:
prometheus_data:
grafana_data:

networks:
job-tracker-net:
driver: bridge
Loading
Loading