404 Not Found

404 Not Found


nginx

Kotlin Project Deployment Explained

The code is written — now the last mile. Charlie containerizes the OrderProcessor, orchestrates the CI/CD pipeline, configures monitoring and alerting, making deployment from git push to production fully automated.

1. What You'll Learn


2. An Architect's Real Story

(1) Pain Point: The Manual Deployment Pipeline

Charlie's team deployed manually: SSH to server → git pullgradle buildjava -jarsystemctl restart. One deployment took 30 minutes, with 2-3 human errors per month.

(2) Fully Automated CI/CD Solution

TEXT
Before: git push → SSH → build → deploy (30 min, error-prone)
After:  git push → GitHub Actions → Docker build → deploy (5 min, zero-touch)

Containerization + CI/CD = deployment goes from 30 minutes of manual effort to a 5-minute fully automated pipeline.


3. Docker Multi-Stage Build

(1) Dockerfile

DOCKERFILE
# Stage 1: Build
FROM gradle:8.5-jdk17 AS builder
WORKDIR /app
COPY build.gradle.kts settings.gradle.kts ./
COPY gradle ./gradle
COPY src ./src
RUN gradle bootJar --no-daemon -x test

# Stage 2: Runtime (lightweight JRE)
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar

# Non-root user for security
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget -qO- http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["java", "-jar", "app.jar"]

(2) Multi-Stage vs Single-Stage Comparison

Dimension Single-Stage Multi-Stage
Image size ~800MB (JDK + source) ~150MB (JRE only)
Security Source in image Source not in runtime image
Build caching No layering Independent layer caching per stage
Build time Full rebuild every time Dependency layer cache reuse

4. Docker Compose Orchestration

(1) docker-compose.yml

YAML
version: '3.8'

services:
  order-processor:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - SPRING_DATASOURCE_URL=r2dbc:postgresql://postgres:5432/orderdb
      - SPRING_DATASOURCE_USERNAME=order_user
      - SPRING_DATASOURCE_PASSWORD=order_pass
      - SPRING_REDIS_HOST=redis
      - MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,prometheus
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - order-net

  postgres:
    image: postgres:16-alpine
    environment:
      - POSTGRES_DB=orderdb
      - POSTGRES_USER=order_user
      - POSTGRES_PASSWORD=order_pass
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U order_user -d orderdb"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - order-net

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
    networks:
      - order-net

  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    networks:
      - order-net

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    depends_on:
      - prometheus
    networks:
      - order-net

volumes:
  pgdata:

networks:
  order-net:
    driver: bridge

(2) One-Click Deployment

BASH
# Start all services
docker-compose up -d

# Check status
docker-compose ps

# View logs
docker-compose logs -f order-processor

# Stop all
docker-compose down

5. CI/CD Pipeline

(1) GitHub Actions

YAML
# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Cache Gradle
        uses: actions/cache@v3
        with:
          path: ~/.gradle/caches
          key: gradle-${{ hashFiles('**/*.gradle.kts') }}

      - name: Run tests
        run: ./gradlew test

      - name: Build JAR
        run: ./gradlew bootJar

      - name: Build Docker image
        run: docker build -t order-processor:${{ github.sha }} .

      - name: Push to registry
        if: github.ref == 'refs/heads/main'
        run: |
          docker tag order-processor:${{ github.sha }} registry.example.com/order-processor:latest
          docker push registry.example.com/order-processor:latest

      - name: Deploy
        if: github.ref == 'refs/heads/main'
        run: |
          ssh deploy@prod-server "docker pull registry.example.com/order-processor:latest && docker-compose up -d"

(2) CI/CD Pipeline Diagram

100%
flowchart TD
    A[git push] --> B[GitHub Actions]
    B --> C[Checkout Code]
    C --> D[Setup JDK 17]
    D --> E[Cache Gradle]
    E --> F[Run Tests]
    F --> G{Tests Pass?}
    G -->|Yes| H[Build JAR]
    G -->|No| I[Notify Team]
    H --> J[Build Docker Image]
    J --> K{Main Branch?}
    K -->|Yes| L[Push to Registry]
    K -->|No| M[Stop]
    L --> N[Deploy to Production]
    N --> O[Health Check]
    O --> P{Healthy?}
    P -->|Yes| Q[Live]
    P -->|No| R[Rollback]

6. Monitoring

(1) Spring Boot Actuator Configuration

KOTLIN
// application.yml
// management:
//   endpoints:
//     web:
//       exposure:
//         include: health,info,prometheus,metrics
//   metrics:
//     export:
//       prometheus:
//         enabled: true
//   endpoint:
//     health:
//       show-details: always

(2) Micrometer Custom Metrics

KOTLIN
import io.micrometer.core.instrument.Counter
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.Timer

class OrderMetrics(registry: MeterRegistry) {
    private val ordersCreated = Counter.builder("orders.created.total")
        .description("Total orders created")
        .register(registry)

    private val orderProcessingTime = Timer.builder("orders.processing.time")
        .description("Order processing time")
        .register(registry)

    fun recordOrderCreated() { ordersCreated.increment() }

    fun <T> recordProcessingTime(block: () -> T): T {
        return orderProcessingTime.recordCallable { block() } ?: block()
    }
}

(3) Monitoring Metrics

Metric Type Alert Threshold
orders_created_total Counter
orders_processing_time Timer P99 > 2s
jvm_memory_used_bytes Gauge > 80%
db_connection_pool_active Gauge > 90%
http_server_requests_seconds Timer P99 > 5s
disk_free_bytes / disk_total_bytes Gauge Free < 10%

7. Health Checks and Production Readiness

(1) Health Check Endpoint

KOTLIN
// Spring Boot Actuator health endpoint
// GET /actuator/health
// {
//   "status": "UP",
//   "components": {
//     "db": { "status": "UP" },
//     "redis": { "status": "UP" },
//     "diskSpace": { "status": "UP" }
//   }
// }

(2) Production Readiness Checklist

Category Check Item Status
Security Non-root user running
Security No hardcoded secrets
Security HTTPS configured
Reliability Health check endpoint
Reliability Graceful shutdown (SIGTERM)
Reliability Database connection pool configured
Observability Structured logging output
Observability Prometheus metrics exposed
Observability Alert rules configured
Performance JVM heap size configured
Performance GC strategy selected
Deployment Docker image < 200MB
Deployment CI/CD pipeline
Deployment Rollback strategy

8. Complete Example: One-Click Deployment Demo

KOTLIN
// ============================================
// OrderProcessor - Deployment Simulation
// Feature: Docker + CI/CD + Health check demo
// ============================================

import kotlin.system.measureTimeMillis

data class DeployResult(val service: String, val status: String, val time: Long)

class DeploySimulator {
    private val services = mutableListOf<DeployResult>()
    private var deployed = false

    fun build(): DeploySimulator {
        print("  Building Docker image...")
        val time = measureTimeMillis { Thread.sleep(800) }
        println(" Done (${time}ms)")
        return this
    }

    fun test(): DeploySimulator {
        print("  Running tests...")
        val time = measureTimeMillis { Thread.sleep(300) }
        println(" Passed (${time}ms)")
        return this
    }

    fun push(): DeploySimulator {
        print("  Pushing to registry...")
        val time = measureTimeMillis { Thread.sleep(500) }
        println(" Done (${time}ms)")
        return this
    }

    fun deploy(service: String, port: Int): DeploySimulator {
        print("  Deploying $service on port $port...")
        val time = measureTimeMillis { Thread.sleep(400) }
        services.add(DeployResult(service, "RUNNING", time))
        println(" Running (${time}ms)")
        return this
    }

    fun healthCheck(): DeploySimulator {
        print("  Health check...")
        val time = measureTimeMillis { Thread.sleep(200) }
        val allHealthy = services.all { it.status == "RUNNING" }
        println(if (allHealthy) " ALL HEALTHY" else " UNHEALTHY DETECTED")
        return this
    }

    fun summary() {
        println("\n=== Deployment Summary ===")
        services.forEach { s ->
            println("  ${s.service}: ${s.status} (${s.time}ms)")
        }
        println("\n  Total services: ${services.size}")
        println("  Health: ${if (services.all { it.status == "RUNNING" }) "ALL GREEN" else "ISSUES DETECTED"}")
        deployed = true
    }

    fun isDeployed() = deployed
}

fun main() {
    println("=== OrderProcessor CI/CD Pipeline ===\n")

    println("[1/6] Build Stage:")
    DeploySimulator()
        .build()
        .test()

    println("\n[2/6] Push Stage:")
    DeploySimulator().push()

    println("\n[3/6] Deploy Stage:")
    val deployer = DeploySimulator()
        .deploy("postgres", 5432)
        .deploy("redis", 6379)
        .deploy("order-processor", 8080)
        .deploy("prometheus", 9090)
        .deploy("grafana", 3000)

    println("\n[4/6] Health Check:")
    deployer.healthCheck()

    println("\n[5/6] Smoke Test:")
    println("  GET /actuator/health -> 200 OK")
    println("  GET /api/v1/orders -> 200 OK")

    println("\n[6/6] Production Ready Checklist:")
    val checks = listOf(
        "Non-root user" to true,
        "No hardcoded secrets" to true,
        "Health endpoint exposed" to true,
        "Prometheus metrics enabled" to true,
        "Graceful shutdown configured" to true,
        "Docker image < 200MB" to true,
        "CI/CD pipeline active" to true,
        "Rollback strategy defined" to true
    )
    checks.forEach { (item, passed) ->
        println("  ${if (passed) "✅" else "❌"} $item")
    }

    val passCount = checks.count { it.second }
    println("\n  Result: $passCount/${checks.size} checks passed")

    if (passCount == checks.size) {
        println("\n  🚀 OrderProcessor is LIVE!")
    }
}

Output:

TEXT
=== OrderProcessor CI/CD Pipeline ===

[1/6] Build Stage:
  Building Docker image... Done (804ms)
  Running tests... Passed (301ms)

[2/6] Push Stage:
  Pushing to registry... Done (502ms)

[3/6] Deploy Stage:
  Deploying postgres on port 5432... Running (401ms)
  Deploying redis on port 6379... Running (401ms)
  Deploying order-processor on port 8080... Running (401ms)
  Deploying prometheus on port 9090... Running (401ms)
  Deploying grafana on port 3000... Running (401ms)

[4/6] Health Check:
  Health check... ALL HEALTHY

[5/6] Smoke Test:
  GET /actuator/health -> 200 OK
  GET /api/v1/orders -> 200 OK

[6/6] Production Ready Checklist:
  ✅ Non-root user
  ✅ No hardcoded secrets
  ✅ Health endpoint exposed
  ✅ Prometheus metrics enabled
  ✅ Graceful shutdown configured
  ✅ Docker image < 200MB
  ✅ CI/CD pipeline active
  ✅ Rollback strategy defined

  Result: 8/8 checks passed

  🚀 OrderProcessor is LIVE!

❓ FAQ

Q What are the benefits of Docker multi-stage builds?
A The final image only contains runtime dependencies (JRE) — no source code or build tools. Image shrinks from 800MB to 150MB, with a smaller attack surface and faster startup.
Q How to achieve zero-downtime deployment?
A Blue-green deployment or rolling updates. Blue-green maintains two environments and switches between them. Rolling updates replace instances one by one. Kubernetes natively supports rolling updates.
Q How to rollback a failed deployment?
A Tag each Docker image build with the Git SHA, then rollback by simply docker run registry.example.com/order-processor:<previous-sha>. Automated CI/CD rollback is even better.
Q How to manage secrets?
A Never store secrets in environment variables or config files. Use Vault, AWS Secrets Manager, or Kubernetes Secrets. CI/CD injects them from the secret manager.
Q How to monitor a JVM application?
A Spring Boot Actuator + Micrometer + Prometheus + Grafana is the standard combo. JVM-specific metrics: heap memory, GC count/time, thread count.
Q How to configure JVM parameters for production?
A Set -Xms and -Xmx to the same value (avoids heap resizing overhead). Use G1GC (-XX:+UseG1GC). In containers, add -XX:+UseContainerSupport.

📖 Summary


📝 Exercises

  1. Beginner (⭐): Write a Dockerfile for the OrderProcessor (single-stage is fine), based on eclipse-temurin:17-jre. Hint: COPY build/libs/*.jar app.jar
  2. Intermediate (⭐⭐): Write a docker-compose.yml containing OrderProcessor + PostgreSQL with health checks configured. Hint: depends_on with condition: service_healthy
  3. Advanced (⭐⭐⭐): Write a complete GitHub Actions CI/CD pipeline including build, test, Docker push, and deploy steps. Hint: on: push: branches: [main]

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏