Skip to main content

πŸš€ Deployment

πŸ“– What is Deployment?​

Deployment is the process of running your developed application in a real production environment. Using Docker, you can run it identically anywhere!

πŸ’‘ JAR Build​

build.gradle.kts Configuration​

plugins {
application
kotlin("jvm") version "1.9.0"
id("io.ktor.plugin") version "2.3.5"
}

application {
mainClass.set("com.example.ApplicationKt")
}

ktor {
fatJar {
archiveFileName.set("app.jar")
}
}

Build Command​

# JAR 파일 생성
./gradlew buildFatJar

# μ‹€ν–‰
java -jar build/libs/app.jar

🎯 Environment Configuration​

application.conf​

ktor {
deployment {
port = 8080
port = ${?PORT} # ν™˜κ²½ λ³€μˆ˜ μš°μ„ 
}

application {
modules = [ com.example.ApplicationKt.module ]
}
}

database {
url = "jdbc:postgresql://localhost:5432/mydb"
url = ${?DATABASE_URL}
user = "admin"
user = ${?DATABASE_USER}
password = "password"
password = ${?DATABASE_PASSWORD}
}

Using Environment Variables​

fun Application.module() {
val dbUrl = environment.config.property("database.url").getString()
val dbUser = environment.config.property("database.user").getString()
val dbPassword = environment.config.property("database.password").getString()

Database.connect(
url = dbUrl,
user = dbUser,
password = dbPassword
)

routing {
get("/health") {
call.respondText("OK")
}
}
}

🐳 Docker Deployment​

Dockerfile​

# 1단계: λΉŒλ“œ
FROM gradle:8.4-jdk17 AS build
WORKDIR /app

COPY build.gradle.kts settings.gradle.kts ./
COPY src ./src

RUN gradle buildFatJar --no-daemon

# 2단계: μ‹€ν–‰
FROM openjdk:17-jdk-slim
WORKDIR /app

COPY --from=build /app/build/libs/app.jar ./app.jar

EXPOSE 8080

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

docker-compose.yml​

version: '3.8'

services:
app:
build: .
ports:
- "8080:8080"
environment:
- DATABASE_URL=jdbc:postgresql://db:5432/mydb
- DATABASE_USER=admin
- DATABASE_PASSWORD=secret
depends_on:
- db

db:
image: postgres:15
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=secret
volumes:
- db-data:/var/lib/postgresql/data
ports:
- "5432:5432"

volumes:
db-data:

Execution​

# λΉŒλ“œ 및 μ‹€ν–‰
docker-compose up -d

# 둜그 확인
docker-compose logs -f app

# 쀑지
docker-compose down

πŸ”§ Production Configuration​

Logging​

import org.slf4j.LoggerFactory

val logger = LoggerFactory.getLogger("Application")

fun Application.module() {
logger.info("μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹œμž‘")

routing {
get("/") {
logger.info("/ μš”μ²­")
call.respondText("Hello!")
}
}
}

CORS Configuration​

import io.ktor.server.plugins.cors.routing.*

fun Application.module() {
install(CORS) {
allowHost("myapp.com", schemes = listOf("https"))
allowHeader(HttpHeaders.ContentType)
allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Get)
allowMethod(HttpMethod.Post)
allowCredentials = true
}
}

Compression​

import io.ktor.server.plugins.compression.*

fun Application.module() {
install(Compression) {
gzip {
priority = 1.0
}
deflate {
priority = 10.0
minimumSize(1024)
}
}
}

πŸ›‘οΈ Security​

HTTPS Configuration​

embeddedServer(Netty, environment = applicationEngineEnvironment {
connector {
port = 8080
}

sslConnector(
keyStore = KeyStore.getInstance("PKCS12").apply {
load(FileInputStream("keystore.p12"), "password".toCharArray())
},
keyAlias = "myalias",
keyStorePassword = { "password".toCharArray() },
privateKeyPassword = { "password".toCharArray() }
) {
port = 8443
}
}).start(wait = true)

Rate Limiting (Simple Version)​

class RateLimiter {
private val requests = mutableMapOf<String, MutableList<Long>>()
private val limit = 100 // 100 requests
private val windowMs = 60_000L // per minute

fun isAllowed(key: String): Boolean {
val now = System.currentTimeMillis()
val userRequests = requests.getOrPut(key) { mutableListOf() }

// 였래된 μš”μ²­ 제거
userRequests.removeIf { it < now - windowMs }

return if (userRequests.size < limit) {
userRequests.add(now)
true
} else {
false
}
}
}

fun Application.module() {
val rateLimiter = RateLimiter()

routing {
intercept(ApplicationCallPipeline.Call) {
val ip = call.request.origin.remoteHost

if (!rateLimiter.isAllowed(ip)) {
call.respond(HttpStatusCode.TooManyRequests)
finish()
}
}

get("/api/data") {
call.respondText("데이터")
}
}
}

🎯 Monitoring​

Health Check​

fun Application.module() {
routing {
get("/health") {
val dbHealth = try {
transaction {
exec("SELECT 1") {}
}
"ok"
} catch (e: Exception) {
"error"
}

call.respond(
mapOf(
"status" to "ok",
"database" to dbHealth,
"timestamp" to System.currentTimeMillis()
)
)
}
}
}

Metrics​

class Metrics {
private val requestCount = AtomicInteger(0)
private val errorCount = AtomicInteger(0)

fun incrementRequests() = requestCount.incrementAndGet()
fun incrementErrors() = errorCount.incrementAndGet()

fun getMetrics() = mapOf(
"requests" to requestCount.get(),
"errors" to errorCount.get()
)
}

fun Application.module() {
val metrics = Metrics()

routing {
intercept(ApplicationCallPipeline.Monitoring) {
metrics.incrementRequests()
try {
proceed()
} catch (e: Exception) {
metrics.incrementErrors()
throw e
}
}

get("/metrics") {
call.respond(metrics.getMetrics())
}
}
}

πŸ”₯ Real-World Deployment Example​

Complete Configuration​

// Application.kt
fun main(args: Array<String>) {
EngineMain.main(args)
}

fun Application.module() {
// μ„€μ • λ‘œλ“œ
val config = environment.config

// λ‘œκΉ…
install(CallLogging) {
level = Level.INFO
}

// CORS
install(CORS) {
allowHost(config.property("cors.host").getString())
allowCredentials = true
}

// μ••μΆ•
install(Compression)

// JSON
install(ContentNegotiation) {
json()
}

// μ—λŸ¬ 처리
install(StatusPages) {
exception<Throwable> { call, cause ->
log.error("Unhandled exception", cause)
call.respond(
HttpStatusCode.InternalServerError,
mapOf("error" to "Internal server error")
)
}
}

// λ°μ΄ν„°λ² μ΄μŠ€
configureDatabases(config)

// λΌμš°νŒ…
configureRouting()
}

fun Application.configureDatabases(config: ApplicationConfig) {
val url = config.property("database.url").getString()
val user = config.property("database.user").getString()
val password = config.property("database.password").getString()

Database.connect(
url = url,
user = user,
password = password
)
}

πŸ€” Frequently Asked Questions​

Q1. Where should I deploy?​

A: Cloud services are recommended!

# AWS (Elastic Beanstalk, ECS)
# Google Cloud (Cloud Run)
# Azure (App Service)
# DigitalOcean (App Platform)
# Heroku

# Docker만 있으면 μ–΄λ””λ“  κ°€λŠ₯!

Q2. What about zero-downtime deployment?​

A: Use Blue-Green deployment or Rolling Update!

# docker-compose.yml (Rolling Update)
version: '3.8'

services:
app:
image: myapp:latest
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s

Q3. How do I manage environment-specific configurations?​

A: Use profiles!

// application-dev.conf
ktor {
deployment {
port = 8080
}
}

// application-prod.conf
ktor {
deployment {
port = 80
}
}

// μ‹€ν–‰
java -jar app.jar -config=application-prod.conf

🎬 Conclusion​

For safe and efficient deployment!

핡심 정리:
βœ… JAR λΉŒλ“œλ‘œ μ‹€ν–‰ 파일
βœ… Docker둜 ν™˜κ²½ 톡일
βœ… ν™˜κ²½ λ³€μˆ˜λ‘œ μ„€μ • 뢄리
βœ… Health Check둜 λͺ¨λ‹ˆν„°λ§
βœ… λ³΄μ•ˆ μ„€μ • ν•„μˆ˜

Congratulations! You have completed the Backend series and all Kotlin documentation! πŸŽ‰

Now you can do full-stack development with Kotlin!

Review of the entire series:

  • Basics: Kotlin fundamentals
  • Practical: Practical applications (extension functions, exceptions, files, regex, DSL)
  • Coroutines: Asynchronous programming
  • Testing: Writing tests
  • Backend: Server development and deployment