google-site-verification: google61fe8ba583a51912.html
Dockerizing Spring Boot Applications

Table of Contents

  1. Introduction to Docker
  2. Spring Boot Application Setup
  3. Basic Docker Configuration
  4. Multi-stage Docker Builds
  5. Docker Compose for Development
  6. Production Docker Configuration
  7. Docker Security Best Practices
  8. Monitoring and Logging
  9. Kubernetes Deployment

Introduction to Docker

Docker is a platform that enables developers to package applications and their dependencies into containers. This ensures consistency across different environments and simplifies deployment processes.

Benefits of Dockerizing Spring Boot:

  • Consistency: Same environment across development, testing, and production
  • Isolation: Applications run in isolated containers
  • Portability: Run anywhere Docker is installed
  • Scalability: Easy to scale horizontally
  • Version Control: Track container versions

Spring Boot Application Setup

1. Basic Spring Boot Application

// Application.java @SpringBootApplication @RestController public class DockerDemoApplication { private final Environment environment; public DockerDemoApplication(Environment environment) { this.environment = environment; } public static void main(String[] args) { SpringApplication.run(DockerDemoApplication.class, args); } @GetMapping("/") public String home() { String appName = environment.getProperty("app.name", "Dockerized Spring Boot"); String version = environment.getProperty("app.version", "1.0.0"); String hostname = getHostname(); return String.format(""" Welcome to %s (v%s) Running on: %s Java Version: %s """, appName, version, hostname, System.getProperty("java.version")); } @GetMapping("/health") public ResponseEntity<Map<String, String>> health() { Map<String, String> status = new HashMap<>(); status.put("status", "UP"); status.put("timestamp", Instant.now().toString()); status.put("service", "docker-demo-app"); return ResponseEntity.ok(status); } @GetMapping("/env") public Map<String, String> environment() { Map<String, String> env = new HashMap<>(); env.put("JAVA_HOME", System.getenv("JAVA_HOME")); env.put("SPRING_PROFILES_ACTIVE", environment.getProperty("spring.profiles.active", "default")); env.put("HOSTNAME", getHostname()); return env; } private String getHostname() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { return "unknown"; } } }

2. Application Configuration

# application.yml spring: application: name: docker-demo-app profiles: active: ${SPRING_PROFILES_ACTIVE:dev} datasource: url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:appdb} username: ${DB_USERNAME:app_user} password: ${DB_PASSWORD:password} hikari: maximum-pool-size: 20 minimum-idle: 5 jpa: hibernate: ddl-auto: validate show-sql: false properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true redis: host: ${REDIS_HOST:localhost} port: ${REDIS_PORT:6379} password: ${REDIS_PASSWORD:} app: name: "Dockerized Spring Boot App" version: "1.0.0" description: "Spring Boot application running in Docker" server: port: ${SERVER_PORT:8080} servlet: context-path: / management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always metrics: enabled: true logging: level: com.example: INFO org.springframework.web: INFO pattern: console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" file: name: /var/log/app/application.log
# application-dev.properties spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.username=sa spring.datasource.password= spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true logging.level.com.example=DEBUG
# application-prod.properties spring.jpa.hibernate.ddl-auto=validate spring.jpa.show-sql=false logging.level.com.example=INFO management.endpoints.web.exposure.include=health,info

3. Health Check Component

// DatabaseHealthIndicator.java @Component public class DatabaseHealthIndicator implements HealthIndicator { private final DataSource dataSource; public DatabaseHealthIndicator(DataSource dataSource) { this.dataSource = dataSource; } @Override public Health health() { try (Connection connection = dataSource.getConnection()) { if (connection.isValid(1000)) { return Health.up() .withDetail("database", "Connected") .withDetail("validationQuery", "SUCCESS") .build(); } else { return Health.down() .withDetail("database", "Not connected") .build(); } } catch (SQLException e) { return Health.down(e) .withDetail("database", "Connection failed") .build(); } } } // CustomHealthIndicator.java @Component public class CustomHealthIndicator implements HealthIndicator { @Override public Health health() { try { // Check custom application health double systemLoad = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); long freeMemory = Runtime.getRuntime().freeMemory(); long totalMemory = Runtime.getRuntime().totalMemory(); Health.Builder status = systemLoad < 0.8 ? Health.up() : Health.down(); return status .withDetail("systemLoad", systemLoad) .withDetail("freeMemory", formatBytes(freeMemory)) .withDetail("totalMemory", formatBytes(totalMemory)) .withDetail("memoryUsage", String.format("%.2f%%", (1 - (double) freeMemory / totalMemory) * 100)) .build(); } catch (Exception e) { return Health.down(e).build(); } } private String formatBytes(long bytes) { if (bytes < 1024) return bytes + " B"; else if (bytes < 1024 * 1024) return String.format("%.2f KB", bytes / 1024.0); else if (bytes < 1024 * 1024 * 1024) return String.format("%.2f MB", bytes / (1024.0 * 1024.0)); else return String.format("%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0)); } }

Basic Docker Configuration

1. Simple Dockerfile

# Basic Dockerfile for Spring Boot FROM openjdk:17-jdk-slim # Set working directory WORKDIR /app # Create non-root user for security RUN groupadd -r spring && useradd -r -g spring spring RUN chown -R spring:spring /app USER spring # Copy JAR file COPY target/docker-demo-app-1.0.0.jar app.jar # Expose port EXPOSE 8080 # Set JVM options ENV JAVA_OPTS="-Xmx512m -Xms256m -Djava.security.egd=file:/dev/./urandom" # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1 # Run the application ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

2. Optimized Dockerfile

# Multi-stage Dockerfile for optimized Spring Boot application FROM maven:3.8.6-openjdk-17 AS builder WORKDIR /app # Copy pom.xml and download dependencies COPY pom.xml . RUN mvn dependency:go-offline # Copy source code and build application COPY src ./src RUN mvn clean package -DskipTests # Runtime stage FROM openjdk:17-jdk-slim # Install curl for health checks RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* # Create application user RUN groupadd -r spring && useradd -r -g spring spring # Create application directory WORKDIR /app # Copy JAR from builder stage COPY --from=builder --chown=spring:spring /app/target/*.jar app.jar # Create log directory RUN mkdir -p /var/log/app && chown spring:spring /var/log/app # Switch to non-root user USER spring # Expose application port EXPOSE 8080 # JVM configuration ENV JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport -Djava.security.egd=file:/dev/./urandom" # Health check configuration HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 # Start application ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

3. .dockerignore File

# .dockerignore .git .gitignore README.md **/target/ **/.mvn/ **/mvnw **/mvnw.cmd **/.gitignore **/HELP.md **/*.iml **/.idea/ **/*.log **/logs/ Dockerfile docker-compose.yml **/node_modules/ **/dist/ **/build/ **/.gradle/ **/gradle/ **/*.jar !target/*.jar

Multi-stage Docker Builds

1. Advanced Multi-stage Dockerfile

# Multi-stage build with different JDK versions and optimizations FROM maven:3.8.6-openjdk-17 AS build WORKDIR /workspace/app # Copy pom.xml COPY pom.xml . # Download dependencies in docker cache layer RUN mvn dependency:go-offline -B # Copy source code COPY src src # Build application RUN mvn clean package -DskipTests RUN java -Djarmode=layertools -jar target/*.jar extract --destination target/extracted # Production stage FROM openjdk:17-jre-slim # Install necessary packages RUN apt-get update && \ apt-get install -y --no-install-recommends curl tzdata && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean # Set timezone ENV TZ=UTC RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # Create spring user RUN groupadd -r spring && useradd -r -g spring spring WORKDIR /application # Copy extracted layers COPY --from=build --chown=spring:spring /workspace/app/target/extracted/dependencies/ ./ COPY --from=build --chown=spring:spring /workspace/app/target/extracted/spring-boot-loader/ ./ COPY --from=build --chown=spring:spring /workspace/app/target/extracted/snapshot-dependencies/ ./ COPY --from=build --chown=spring:spring /workspace/app/target/extracted/application/ ./ # Create log directory RUN mkdir -p /var/log/app && chown spring:spring /var/log/app USER spring EXPOSE 8080 # JVM configuration optimized for containers ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -Djava.security.egd=file:/dev/./urandom" ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} org.springframework.boot.loader.JarLauncher"]

2. Jib Configuration (Alternative to Dockerfile)

<!-- Maven Jib Plugin --> <plugin> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-maven-plugin</artifactId> <version>3.3.0</version> <configuration> <from> <image>openjdk:17-jre-slim</image> </from> <to> <image>my-registry/docker-demo-app:${project.version}</image> </to> <container> <creationTime>USE_CURRENT_TIMESTAMP</creationTime> <user>1000</user> <ports> <port>8080</port> </ports> <environment> <JAVA_OPTS>-Xmx512m -Xms256m</JAVA_OPTS> <SPRING_PROFILES_ACTIVE>prod</SPRING_PROFILES_ACTIVE> </environment> <labels> <version>${project.version}</version> <maintainer>[email protected]</maintainer> </labels> <format>OCI</format> </container> </configuration> </plugin>
// Jib build commands // mvn compile jib:build -Dimage=my-registry/docker-demo-app:latest // mvn compile jib:dockerBuild -Dimage=docker-demo-app:latest

Docker Compose for Development

1. Development Docker Compose

# docker-compose.yml version: '3.8' services: app: build: context: . dockerfile: Dockerfile container_name: spring-boot-app ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=dev - JAVA_OPTS=-Xmx512m -Xms256m volumes: - ./logs:/var/log/app - ./config:/app/config depends_on: - postgres - redis networks: - app-network postgres: image: postgres:14-alpine container_name: postgres-db environment: - POSTGRES_DB=appdb - POSTGRES_USER=app_user - POSTGRES_PASSWORD=password ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql networks: - app-network redis: image: redis:7-alpine container_name: redis-cache ports: - "6379:6379" command: redis-server --appendonly yes volumes: - redis_data:/data networks: - app-network pgadmin: image: dpage/pgadmin4 container_name: pgadmin environment: - [email protected] - PGADMIN_DEFAULT_PASSWORD=admin ports: - "5050:80" depends_on: - postgres networks: - app-network redis-commander: image: rediscommander/redis-commander:latest container_name: redis-commander environment: - REDIS_HOSTS=local:redis:6379 ports: - "8081:8081" depends_on: - redis networks: - app-network volumes: postgres_data: redis_data: networks: app-network: driver: bridge

2. Development with Hot Reload

# docker-compose.dev.yml version: '3.8' services: app: build: context: . dockerfile: Dockerfile.dev container_name: spring-boot-app-dev ports: - "8080:8080" - "5005:5005" # Debug port environment: - SPRING_PROFILES_ACTIVE=dev - JAVA_OPTS=-Xmx512m -Xms256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 - SPRING_DEVTOOLS_REMOTE_SECRET=mysecret volumes: - .:/app - ~/.m2:/root/.m2 working_dir: /app command: mvn spring-boot:run networks: - app-network postgres: image: postgres:14-alpine container_name: postgres-db-dev environment: - POSTGRES_DB=appdb - POSTGRES_USER=app_user - POSTGRES_PASSWORD=password ports: - "5432:5432" volumes: - postgres_data_dev:/var/lib/postgresql/data networks: - app-network volumes: postgres_data_dev: networks: app-network: driver: bridge
# Dockerfile.dev FROM maven:3.8.6-openjdk-17 WORKDIR /app # Install curl for health checks RUN apt-get update && apt-get install -y curl EXPOSE 8080 EXPOSE 5005 CMD ["mvn", "spring-boot:run"]

Production Docker Configuration

1. Production Docker Compose

# docker-compose.prod.yml version: '3.8' services: app: image: my-registry/docker-demo-app:${TAG:-latest} container_name: spring-boot-app-prod restart: unless-stopped ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod - JAVA_OPTS=-Xmx512m -Xms256m -XX:+UseG1GC -XX:MaxRAMPercentage=75.0 - DB_HOST=postgres - DB_PORT=5432 - DB_NAME=appdb - DB_USERNAME=${DB_USERNAME} - DB_PASSWORD=${DB_PASSWORD} - REDIS_HOST=redis - REDIS_PASSWORD=${REDIS_PASSWORD} env_file: - .env.production volumes: - app_logs:/var/log/app - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro depends_on: - postgres - redis healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s logging: driver: "json-file" options: max-size: "10m" max-file: "3" networks: - app-network postgres: image: postgres:14-alpine container_name: postgres-db-prod restart: unless-stopped environment: - POSTGRES_DB=appdb - POSTGRES_USER=${DB_USERNAME} - POSTGRES_PASSWORD=${DB_PASSWORD} volumes: - postgres_data_prod:/var/lib/postgresql/data - ./postgres/backup:/backup command: > postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c max_connections=200 healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d appdb"] interval: 30s timeout: 10s retries: 3 logging: driver: "json-file" options: max-size: "10m" max-file: "3" networks: - app-network redis: image: redis:7-alpine container_name: redis-cache-prod restart: unless-stopped command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes volumes: - redis_data_prod:/data healthcheck: test: ["CMD", "redis-cli", "--raw", "incr", "ping"] interval: 30s timeout: 10s retries: 3 logging: driver: "json-file" options: max-size: "10m" max-file: "3" networks: - app-network nginx: image: nginx:alpine container_name: nginx-proxy restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./ssl:/etc/nginx/ssl:ro depends_on: - app networks: - app-network volumes: postgres_data_prod: redis_data_prod: app_logs: networks: app-network: driver: bridge

2. Environment Files

# .env.production # Database Configuration DB_USERNAME=app_user_prod DB_PASSWORD=secure_password_123 DB_NAME=appdb DB_HOST=postgres DB_PORT=5432 # Redis Configuration REDIS_HOST=redis REDIS_PORT=6379 REDIS_PASSWORD=redis_secure_password # Application Configuration SPRING_PROFILES_ACTIVE=prod SERVER_PORT=8080 JAVA_OPTS=-Xmx512m -Xms256m -XX:+UseG1GC # Docker Configuration TAG=1.0.0

3. Nginx Configuration

# nginx/nginx.conf events { worker_connections 1024; } http { upstream spring_app { server app:8080; } server { listen 80; server_name localhost; # Redirect HTTP to HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name localhost; ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512; ssl_prefer_server_ciphers off; # Security headers add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; # Proxy settings location / { proxy_pass http://spring_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Timeout settings proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; } # Health check endpoint location /health { proxy_pass http://spring_app/actuator/health; access_log off; } # Metrics endpoint (internal only) location /metrics { proxy_pass http://spring_app/actuator/metrics; allow 127.0.0.1; deny all; access_log off; } } }

Docker Security Best Practices

1. Secure Dockerfile

# Secure Dockerfile with security best practices FROM openjdk:17-jdk-slim AS builder # Security: Use no-root user during build RUN groupadd -r builder && useradd -r -g builder builder USER builder WORKDIR /home/builder COPY --chown=builder:builder pom.xml . RUN mvn dependency:go-offline -B COPY --chown=builder:builder src ./src RUN mvn clean package -DskipTests # Production stage FROM openjdk:17-jre-slim # Security: Install security updates RUN apt-get update && \ apt-get upgrade -y && \ apt-get install -y --no-install-recommends curl && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean # Security: Create non-root user RUN groupadd -r spring && useradd -r -g spring spring -s /bin/false # Security: Set secure permissions WORKDIR /app RUN chown spring:spring /app && chmod 755 /app # Security: Copy files as non-root user COPY --from=builder --chown=spring:spring /home/builder/target/*.jar app.jar # Security: Create log directory with secure permissions RUN mkdir -p /var/log/app && \ chown spring:spring /var/log/app && \ chmod 755 /var/log/app # Security: Switch to non-root user USER spring # Security: Don't run as root EXPOSE 8080 # Security: Set secure JVM options ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom -Dfile.encoding=UTF-8" # Security: Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 # Security: Use exec form for entrypoint ENTRYPOINT exec java $JAVA_OPTS -jar app.jar

2. Security Scanning and Hardening

# docker-compose.security.yml version: '3.8' services: trivy: image: aquasec/trivy:latest container_name: trivy-scanner command: > sh -c "trivy filesystem --exit-code 1 --no-progress /app" volumes: - .:/app:ro networks: - app-network hadolint: image: hadolint/hadolint:latest container_name: hadolint-linter command: hadolint Dockerfile volumes: - .:/app:ro working_dir: /app networks: - app-network grype: image: anchore/grype:latest container_name: grype-scanner command: > sh -c "grype dir:/app --fail-on high" volumes: - .:/app:ro networks: - app-network

3. Security Configuration in Application

// SecurityConfiguration.java @Configuration @EnableWebSecurity public class SecurityConfiguration { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors().and() .csrf().disable() .authorizeHttpRequests(authz -> authz .requestMatchers("/actuator/health", "/actuator/info").permitAll() .requestMatchers("/actuator/**").hasRole("ADMIN") .anyRequest().authenticated() ) .httpBasic(withDefaults()) .headers(headers -> headers .contentSecurityPolicy("default-src 'self'") .frameOptions().deny() ); return http.build(); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList( "https://mydomain.com", "https://www.mydomain.com" )); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type")); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }

Monitoring and Logging

1. Docker Logging Configuration

# docker-compose.monitoring.yml version: '3.8' services: prometheus: image: prom/prometheus:latest container_name: prometheus ports: - "9090:9090" volumes: - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.path=/prometheus' - '--web.console.libraries=/etc/prometheus/console_libraries' - '--web.console.templates=/etc/prometheus/console_templates' - '--storage.tsdb.retention.time=200h' - '--web.enable-lifecycle' networks: - app-network grafana: image: grafana/grafana:latest container_name: grafana ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin123 volumes: - grafana_data:/var/lib/grafana - ./monitoring/dashboards:/etc/grafana/provisioning/dashboards - ./monitoring/datasources:/etc/grafana/provisioning/datasources depends_on: - prometheus networks: - app-network node-exporter: image: prom/node-exporter:latest container_name: node-exporter ports: - "9100:9100" volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro command: - '--path.procfs=/host/proc' - '--path.rootfs=/rootfs' - '--path.sysfs=/host/sys' - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)' networks: - app-network volumes: prometheus_data: grafana_data: networks: app-network: driver: bridge

2. Prometheus Configuration

# monitoring/prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s rule_files: - "alert_rules.yml" scrape_configs: - job_name: 'spring-boot-app' metrics_path: '/actuator/prometheus' scrape_interval: 10s static_configs: - targets: ['app:8080'] labels: application: 'docker-demo-app' environment: 'production' - job_name: 'node-exporter' static_configs: - targets: ['node-exporter:9100'] - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] alerting: alertmanagers: - static_configs: - targets: - alertmanager:9093

3. Application Monitoring Configuration

// MonitoringConfiguration.java @Configuration public class MonitoringConfiguration { @Bean public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() { return registry -> registry.config().commonTags( "application", "docker-demo-app", "environment", System.getenv().getOrDefault("SPRING_PROFILES_ACTIVE", "default") ); } @Bean public TimedAspect timedAspect(MeterRegistry registry) { return new TimedAspect(registry); } } // CustomMetricsService.java @Service public class CustomMetricsService { private final Counter customCounter; private final Timer customTimer; private final Gauge customGauge; public CustomMetricsService(MeterRegistry registry) { this.customCounter = Counter.builder("app.custom.counter") .description("Custom business events counter") .register(registry); this.customTimer = Timer.builder("app.custom.timer") .description("Custom operation timer") .register(registry); this.customGauge = Gauge.builder("app.custom.gauge") .description("Custom gauge") .register(registry, this, service -> service.getGaugeValue()); } public void recordEvent() { customCounter.increment(); } public void recordTimedOperation(Runnable operation) { customTimer.record(operation); } private double getGaugeValue() { // Return some business metric return Math.random() * 100; } }

Kubernetes Deployment

1. Kubernetes Deployment Manifest

# k8s/deployment.yml apiVersion: apps/v1 kind: Deployment metadata: name: spring-boot-app labels: app: spring-boot-app spec: replicas: 3 selector: matchLabels: app: spring-boot-app template: metadata: labels: app: spring-boot-app annotations: prometheus.io/scrape: "true" prometheus.io/path: "/actuator/prometheus" prometheus.io/port: "8080" spec: containers: - name: spring-boot-app image: my-registry/docker-demo-app:1.0.0 ports: - containerPort: 8080 env: - name: SPRING_PROFILES_ACTIVE value: "prod" - name: JAVA_OPTS value: "-Xmx512m -Xms256m -XX:+UseContainerSupport" - name: DB_HOST valueFrom: secretKeyRef: name: app-secrets key: db-host - name: DB_PASSWORD valueFrom: secretKeyRef: name: app-secrets key: db-password resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" livenessProbe: httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 volumeMounts: - name: app-logs mountPath: /var/log/app volumes: - name: app-logs emptyDir: {} securityContext: runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 --- apiVersion: v1 kind: Service metadata: name: spring-boot-app-service spec: selector: app: spring-boot-app ports: - port: 80 targetPort: 8080 type: LoadBalancer

2. Kubernetes Configuration Files

# k8s/configmap.yml apiVersion: v1 kind: ConfigMap metadata: name: app-config data: application.yml: | spring: application: name: docker-demo-app jpa: show-sql: false hibernate: ddl-auto: validate logging: level: com.example: INFO file: name: /var/log/app/application.log --- # k8s/secrets.yml apiVersion: v1 kind: Secret metadata: name: app-secrets type: Opaque data: db-password: c2VjdXJlX3Bhc3N3b3JkXzEyMw== # base64 encoded redis-password: cmVkaXNfc2VjdXJlX3Bhc3N3b3Jk jwt-secret: anNvbi13ZWItdG9rZW4tc2VjcmV0

3. Build and Deployment Scripts

#!/bin/bash # build-and-deploy.sh set -e # Configuration APP_NAME="docker-demo-app" VERSION="1.0.0" REGISTRY="my-registry" K8S_NAMESPACE="default" echo "Building Docker image..." docker build -t $REGISTRY/$APP_NAME:$VERSION . docker build -t $REGISTRY/$APP_NAME:latest . echo "Pushing images to registry..." docker push $REGISTRY/$APP_NAME:$VERSION docker push $REGISTRY/$APP_NAME:latest echo "Deploying to Kubernetes..." kubectl apply -f k8s/configmap.yml kubectl apply -f k8s/secrets.yml kubectl apply -f k8s/deployment.yml echo "Rolling update..." kubectl rollout restart deployment/spring-boot-app -n $K8S_NAMESPACE echo "Waiting for deployment to complete..." kubectl rollout status deployment/spring-boot-app -n $K8S_NAMESPACE echo "Deployment completed successfully!"
#!/bin/bash # docker-management.sh # Build with different tags docker_build() { local version=$1 docker build -t my-registry/docker-demo-app:$version . docker build -t my-registry/docker-demo-app:latest . } # Security scan docker_scan() { docker scan my-registry/docker-demo-app:latest } # Clean up unused images docker_cleanup() { docker image prune -f docker container prune -f } # View logs docker_logs() { docker-compose logs -f app } # SSH into container docker_ssh() { docker exec -it spring-boot-app /bin/sh } # Performance stats docker_stats() { docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" }

Conclusion

Dockerizing Spring Boot applications provides numerous benefits for development, testing, and production deployment. Key takeaways:

  1. Use multi-stage builds for smaller, more secure images
  2. Implement proper health checks for container orchestration
  3. Follow security best practices (non-root users, regular updates)
  4. Use Docker Compose for development environments
  5. Implement proper monitoring and logging
  6. Optimize JVM settings for container environments
  7. Use .dockerignore to exclude unnecessary files
  8. Consider using Jib for simplified container builds

By following these patterns and best practices, you can create robust, scalable, and maintainable Dockerized Spring Boot applications that run consistently across different environments.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper