Swift en el servidor: Swift NIO, Vapor en Linux, Docker y despliegue en producción

Swift en el servidor es una realidad madura en 2026. El ecosistema se apoya en dos pilares: Swift NIO, el framework de I/O asíncrono de bajo nivel creado por Apple, y Vapor, el framework web de alto nivel más adoptado que corre sobre NIO. Ambos funcionan en Linux, lo que abre la puerta a despliegues en contenedores Docker con imágenes de apenas 80 MB.

Swift NIO: el motor asíncrono

Swift NIO es un framework de I/O sin bloqueo basado en el modelo de event loop. Raramente se usa directamente en aplicaciones; es la base sobre la que corren Vapor, gRPC en Swift y el cliente HTTP de AWS SDK for Swift:

import NIO
import NIOPosix

// Servidor TCP mínimo con NIO
let grupo = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
defer { try? grupo.syncShutdownGracefully() }

let arranque = ServerBootstrap(group: grupo)
    .serverChannelOption(.backlog, value: 256)
    .serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
    .childChannelInitializer { canal in
        canal.pipeline.addHandlers([
            ByteToMessageHandler(LineBasedFrameDecoder()),
            MiManejador()
        ])
    }

let canal = try arranque.bind(host: "0.0.0.0", port: 8080).wait()
print("Servidor escuchando en (canal.localAddress!)")
try canal.closeFuture.wait()

El antipatrón más frecuente con NIO es bloquear el EventLoop con trabajo síncrono pesado. Nunca hagas llamadas síncronas a base de datos, I/O de ficheros o computación intensiva desde el loop:

// MAL: bloquea el EventLoop
eventLoop.execute {
    let resultado = baseDatos.querySync("SELECT...")  // NUNCA
}

// BIEN: delegar a un thread pool
eventLoop.flatSubmit {
    NIOThreadPool.singleton.runIfActive(eventLoop: eventLoop) {
        baseDatos.querySync("SELECT...")
    }
}

Vapor: el framework web

Vapor abstrae NIO con una API de rutas, controladores y middleware similar a Express o Gin:

import Vapor

// Punto de entrada
@main
struct App {
    static func main() async throws {
        var env = try Environment.detect()
        try LoggingSystem.bootstrap(from: &env)
        let app = Application(env)
        defer { app.shutdown() }
        try configure(app)
        try await app.runFromAsyncMainEntrypoint()
    }
}

// Configuración
func configure(_ app: Application) throws {
    app.databases.use(.postgres(
        hostname: Environment.get("DB_HOST") ?? "localhost",
        port: 5432,
        username: Environment.get("DB_USER") ?? "postgres",
        password: Environment.get("DB_PASS") ?? "",
        database: Environment.get("DB_NAME") ?? "miapp"
    ), as: .psql)

    app.migrations.add(CrearProductos())
    try routes(app)
}

Fluent ORM con PostgreSQL

Fluent es el ORM de Vapor, con soporte para PostgreSQL, MySQL y SQLite:

import Fluent
import Vapor

// Modelo
final class Producto: Model, Content, @unchecked Sendable {
    static let schema = "productos"

    @ID(key: .id)
    var id: UUID?

    @Field(key: "nombre")
    var nombre: String

    @Field(key: "precio")
    var precio: Double

    @Timestamp(key: "creado_en", on: .create)
    var creadoEn: Date?

    init() {}
    init(id: UUID? = nil, nombre: String, precio: Double) {
        self.id = id
        self.nombre = nombre
        self.precio = precio
    }
}

// Migración
struct CrearProductos: AsyncMigration {
    func prepare(on database: Database) async throws {
        try await database.schema("productos")
            .id()
            .field("nombre", .string, .required)
            .field("precio", .double, .required)
            .field("creado_en", .datetime)
            .create()
    }

    func revert(on database: Database) async throws {
        try await database.schema("productos").delete()
    }
}

// Controlador REST
struct ProductoController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        let productos = routes.grouped("productos")
        productos.get(use: listar)
        productos.post(use: crear)
        productos.group(":id") { producto in
            producto.get(use: obtener)
            producto.put(use: actualizar)
            producto.delete(use: eliminar)
        }
    }

    func listar(req: Request) async throws -> [Producto] {
        try await Producto.query(on: req.db).all()
    }

    func crear(req: Request) async throws -> Producto {
        let producto = try req.content.decode(Producto.self)
        try await producto.save(on: req.db)
        return producto
    }

    func obtener(req: Request) async throws -> Producto {
        guard let producto = try await Producto.find(req.parameters.get("id"), on: req.db) else {
            throw Abort(.notFound)
        }
        return producto
    }
}

Dockerfile multi-stage para Swift

La imagen oficial de Swift es grande (~1.5 GB). Con un build multi-stage se compila en la imagen completa y se copia solo el binario a una imagen base de Ubuntu minimal:

// Dockerfile
// ---- Build stage ----
FROM swift:6.0-jammy AS build
WORKDIR /build
COPY Package.swift Package.resolved ./
RUN swift package resolve
COPY Sources ./Sources
RUN swift build -c release --static-swift-stdlib

// ---- Runtime stage ----
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y libssl3 libcurl4 && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /build/.build/release/Run ./
EXPOSE 8080
ENTRYPOINT ["./Run"]

docker-compose para desarrollo

// docker-compose.yml
// services:
//   app:
//     build: .
//     ports: ["8080:8080"]
//     environment:
//       - DB_HOST=postgres
//       - DB_USER=vapor
//       - DB_PASS=secreto
//       - DB_NAME=miapp
//     depends_on: [postgres]
//   postgres:
//     image: postgres:16
//     environment:
//       - POSTGRES_USER=vapor
//       - POSTGRES_PASSWORD=secreto
//       - POSTGRES_DB=miapp
//     volumes:
//       - pgdata:/var/lib/postgresql/data
// volumes:
//   pgdata:

Resumen

Swift en el servidor es una pila coherente: NIO gestiona el I/O asíncrono sin bloqueos; Vapor añade la capa web con rutas, middleware y gestión de errores; Fluent conecta con PostgreSQL con un ORM fluent en Swift; y Docker multi-stage reduce la imagen de producción a unos 80 MB. El rendimiento es comparable al de Go para APIs CRUD típicas, con la ventaja de compartir modelos y lógica de negocio con la app iOS sin duplicar código.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP