Agent skill

spring-boot-deployment

Guide for deploying Spring Boot applications including Docker, health checks, and production configurations. Use this when preparing applications for deployment or setting up CI/CD.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/spring-boot-deployment

SKILL.md

Spring Boot Deployment Best Practices

Follow these practices for production-ready deployments.

Dockerfile Best Practices

dockerfile
# Multi-stage build for smaller image
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY gradle gradle
COPY gradlew build.gradle settings.gradle ./
COPY src src

# Build the application
RUN ./gradlew bootJar --no-daemon

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

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

# Copy the built jar
COPY --from=builder /app/build/libs/*.jar app.jar

# JVM tuning for containers
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

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

EXPOSE 8082

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Docker Compose Configuration

yaml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8082:8082"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - DATABASE_URL=jdbc:postgresql://db:5432/salonhub
      - DATABASE_USERNAME=postgres
      - DATABASE_PASSWORD=${DB_PASSWORD}
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8082/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=salonhub
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  postgres_data:

Health Checks and Probes

yaml
# application-prod.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  
  endpoint:
    health:
      show-details: when-authorized
      probes:
        enabled: true
  
  health:
    livenessState:
      enabled: true
    readinessState:
      enabled: true
    db:
      enabled: true
java
// Custom health indicator
@Component
public class DatabaseHealthIndicator implements HealthIndicator {

    private final DataSource dataSource;

    public DatabaseHealthIndicator(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public Health health() {
        try (Connection conn = dataSource.getConnection()) {
            PreparedStatement ps = conn.prepareStatement("SELECT 1");
            ps.executeQuery();
            return Health.up()
                .withDetail("database", conn.getMetaData().getDatabaseProductName())
                .withDetail("version", conn.getMetaData().getDatabaseProductVersion())
                .build();
        } catch (SQLException e) {
            return Health.down()
                .withDetail("error", e.getMessage())
                .build();
        }
    }
}

Production Configuration

yaml
# application-prod.yml
spring:
  datasource:
    url: ${DATABASE_URL}
    hikari:
      maximum-pool-size: 10
      minimum-idle: 5
      connection-timeout: 20000
      idle-timeout: 300000
      max-lifetime: 1200000

  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    open-in-view: false

server:
  port: 8082
  shutdown: graceful
  tomcat:
    connection-timeout: 20000
    max-connections: 10000
    accept-count: 100
    threads:
      max: 200
      min-spare: 10

logging:
  level:
    root: WARN
    com.salonhub.api: INFO
  pattern:
    console: '{"time":"%d","level":"%p","logger":"%logger","message":"%m"}%n'

Graceful Shutdown

yaml
server:
  shutdown: graceful

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s
java
// Handle cleanup on shutdown
@Component
public class GracefulShutdown {

    private static final Logger logger = LoggerFactory.getLogger(GracefulShutdown.class);

    @PreDestroy
    public void onShutdown() {
        logger.info("Application shutting down...");
        // Cleanup resources, finish pending operations
    }
}

Environment Variables

bash
# Required production environment variables
DATABASE_URL=jdbc:postgresql://localhost:5432/salonhub
DATABASE_USERNAME=postgres
DATABASE_PASSWORD=<secure-password>
JWT_SECRET=<secure-random-string-min-256-bits>
SPRING_PROFILES_ACTIVE=prod

Render.yaml (Cloud Deployment)

yaml
services:
  - type: web
    name: salon-hub-api
    env: docker
    dockerfilePath: ./Dockerfile
    plan: starter
    healthCheckPath: /actuator/health
    envVars:
      - key: SPRING_PROFILES_ACTIVE
        value: prod
      - key: DATABASE_URL
        fromDatabase:
          name: salon-hub-db
          property: connectionString
      - key: JWT_SECRET
        generateValue: true

databases:
  - name: salon-hub-db
    databaseName: salonhub
    user: postgres
    plan: starter

CI/CD Pipeline (GitHub Actions)

yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: '21'
          distribution: 'temurin'
      
      - name: Run tests
        run: ./gradlew check
      
      - name: Build JAR
        run: ./gradlew bootJar

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Deploy to Render
        env:
          RENDER_API_KEY: ${{ secrets.RENDER_API_KEY }}
        run: |
          curl -X POST \
            -H "Authorization: Bearer $RENDER_API_KEY" \
            https://api.render.com/deploy/srv-xxx

Logging for Production

yaml
logging:
  level:
    root: WARN
    com.salonhub.api: INFO
  
  # JSON format for log aggregation
  pattern:
    console: '{"timestamp":"%d{ISO8601}","level":"%p","thread":"%t","logger":"%logger{36}","message":"%m","exception":"%ex"}%n'

Security Hardening

yaml
# Disable sensitive actuator endpoints in production
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    env:
      enabled: false
    beans:
      enabled: false
    configprops:
      enabled: false

Deployment Checklist

Before Deployment

  • All tests passing: ./gradlew check
  • Security scan passed
  • Configuration validated for target environment
  • Database migrations tested
  • Environment variables documented

Deployment

  • Build Docker image
  • Push to container registry
  • Deploy with health checks enabled
  • Verify health endpoints responding

After Deployment

  • Monitor application logs
  • Check health endpoints
  • Verify database connectivity
  • Test critical endpoints
  • Monitor metrics and alerts

Didn't find tool you were looking for?

Be as detailed as possible for better results