Introduction: Why Swift and Kubernetes?
Swift is not just a language for iOS and macOS. Thanks to the Swift on Server project and frameworks like Vapor and SwiftNIO, Swift developers can build high-performance server-side applications. However, for such an application to run stably and scale, a modern infrastructure is required.
Kubernetes (K8s) is the de facto standard for container orchestration. In this guide, we will show how to package a Swift application into a Docker image, write Kubernetes manifests, and deploy it in a cluster. You will learn how to use SwiftNIO to create a lightweight HTTP server and configure it for cloud deployment.
1. Preparing the Swift Application for Containerization
1.1. Creating a SwiftNIO Project
For demonstration, we will write a minimal HTTP server using SwiftNIO. This avoids unnecessary dependencies and shows how low-level networking works. Open a terminal and create a new project:
mkdir SwiftK8sDemocd SwiftK8sDemoswift package init --type executableAdd the SwiftNIO dependency to Package.swift:
// swift-tools-version:5.7import PackageDescription
let package = Package( name: "SwiftK8sDemo", dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0") ], targets: [ .executableTarget( name: "SwiftK8sDemo", dependencies: [ .product(name: "NIO", package: "swift-nio"), .product(name: "NIOHTTP1", package: "swift-nio") ] ) ])1.2. Server Code with Health Check
Let's create a simple server that responds to GET requests and has a health check endpoint (/health). This is necessary for Kubernetes liveness probes. Replace the contents of Sources/SwiftK8sDemo/main.swift:
import NIOimport NIOHTTP1
final class HTTPHandler: ChannelInboundHandler { typealias InboundIn = HTTPServerRequestPart typealias OutboundOut = HTTPServerResponsePart
func channelRead(context: ChannelHandlerContext, data: NIOAny) { let req = unwrapInboundIn(data) switch req { case .head(let header): let response: String let status: HTTPResponseStatus if header.uri == "/health" { response = "{\\"status\\":\\"ok\\"}" status = .ok } else { response = "Hello from Swift on K8s!" status = .ok } var buffer = context.channel.allocator.buffer(capacity: response.utf8.count) buffer.writeString(response) let head = HTTPResponseHead(version: .init(major: 1, minor: 1), status: status) context.write(wrapOutboundOut(.head(head)), promise: nil) context.write(wrapOutboundOut(.body(.byteBuffer(buffer))), promise: nil) context.writeAndFlush(wrapOutboundOut(.end(nil)), promise: nil) default: break } }}
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)defer { try! group.syncShutdownGracefully() }
let bootstrap = ServerBootstrap(group: group) .serverChannelOption(ChannelOptions.backlog, value: 256) .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) .childChannelInitializer { channel in channel.pipeline.configureHTTPServerPipeline().flatMap { channel.pipeline.addHandler(HTTPHandler()) } }
let host = ProcessInfo.processInfo.environment["HOST"] ?? "0.0.0.0"let port = Int(ProcessInfo.processInfo.environment["PORT"] ?? "8080") ?? 8080
print("Server starting on \\(host):\\(port)")try bootstrap.bind(host: host, port: port).wai