Por qué Swift y Docker son la pareja ideal para el desarrollo backend
\n\nSi está acostumbrado a pensar que Swift es solo desarrollo móvil para iOS, es hora de sorprenderse. Swift avanza con paso firme hacia el lado del servidor: Vapor, Kitura, Perfect — frameworks que permiten escribir backends rápidos y seguros. Pero hay un problema: ¿cómo garantizar que su aplicación funcione igual en la Mac del desarrollador y en el servidor de producción con Ubuntu?
\n\nLa respuesta es la contenerización de aplicaciones Swift con Docker. Docker permite empaquetar todo: el binario en sí, las bibliotecas del sistema, las dependencias de Swift Package Manager (SPM) — y ejecutarlo en cualquier lugar. Nada de «en mi máquina funciona» y «en la tuya no».
\n\nEn este artículo, veremos cómo compilar una aplicación Swift en una imagen Docker, optimizar su tamaño, configurar una compilación de múltiples etapas e incluso agregar recarga en caliente para el desarrollo. ¡Allá vamos!
\n\nPaso 1. Preparación de la aplicación Swift
\n\nComo ejemplo, tomaremos una aplicación web mínima con Vapor. Si aún no tiene un proyecto, créelo:
\n\nmkdir MySwiftApp && cd MySwiftApp\nswift package init --type executable\n\nAñadimos Vapor en Package.swift:
// swift-tools-version:5.7\nimport PackageDescription\n\nlet package = Package(\n name: "MySwiftApp",\n dependencies: [\n .package(url: "https://github.com/vapor/vapor.git", from: "4.89.0")\n ],\n targets: [\n .executableTarget(\n name: "MySwiftApp",\n dependencies: [\n .product(name: "Vapor", package: "vapor")\n ]\n )\n ]\n)\n\nEl main.swift más simple:
\n\nimport Vapor\n\nvar env = try Environment.detect()\ntry LoggingSystem.bootstrap(from: &env)\nlet app = Application(env)\ndefer { app.shutdown() }\n\napp.get("hello") { req -> String in\n return "Hello from Swift in Docker!"\n}\n\ntry app.run()\n\nVerifique que el proyecto compile localmente: swift build. Si todo está bien, pasamos a la contenerización.
Paso 2. Escribir el Dockerfile para Swift
\n\nEl secreto principal de una buena contenerización de Swift es la compilación de múltiples etapas. En la primera etapa, compilamos el binario; en la segunda, copiamos solo el binario y las dependencias de tiempo de ejecución. La imagen final resulta ser de 5 a 10 veces más pequeña.
\n\n# ---- Etapa 1: Compilación ----\nFROM swift:5.9-jammy AS build\n\nWORKDIR /app\n\n# Copiamos Package.swift y Package.resolved para cachear las dependencias\nCOPY Package.swift ./\nCOPY Package.resolved ./\n\n# Descargamos las dependencias por separado — esto se cachea con Docker\nRUN swift package resolve\n\n# Copiamos el código fuente\nCOPY Sources ./Sources\n\n# Compilación en modo release\nRUN swift build -c release --static-swift-stdlib\n\n# ---- Etapa 2: Runtime ----\nFROM ubuntu:jammy AS runtime\n\n# Instalamos las dependencias mínimas para el runtime de Swift\nRUN apt-get update && apt-get install -y \\\n libicu70 \\\n libcurl4 \\\n libxml2 \\\n && rm -rf /var/lib/apt/lists/*\n\nWORKDIR /app\n\n# Copiamos solo el binario desde la etapa de compilación\nCOPY --from=build /app/.build/release/MySwiftApp .\n\n# Puerto que Vapor escucha por defecto\nEXPOSE 8080\n\nCMD ["./MySwiftApp"]\n\nPuntos clave:
\n- \n
--static-swift-stdlib— enlace estático de la biblioteca estándar, para no arrastrar libswiftCore.so en tiempo de ejecución. \n - Separación de COPY — primero
Package.swift, luego el código. Así Docker usa la caché de capas y no recompila las dependencias al cambiar el código fuente. \n - La imagen base
ubuntu:jammypesa ~80 MB, la imagen final pesa alrededor de 100-150 MB. \n
Paso 3. Compilación y ejecución del contenedor
\n\nCompilamos la imagen:
\n\ndocker build -t my-swift-app .\n\nEjecutamos:
\n\ndocker run -p 8080:8080 my-swift-app\n\nO