Контейнеризация Swift-приложений с Docker: полный гайд для разработчика

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему Swift и Docker — идеальная пара для серверной разработки

Если вы привыкли думать, что Swift — это только мобильная разработка под iOS, пришло время удивиться. Swift уверенно шагает на серверную сторону: Vapor, Kitura, Perfect — фреймворки, которые позволяют писать быстрые и безопасные бэкенды. Но есть одна проблема: как гарантировать, что ваше приложение будет работать одинаково на Mac разработчика и на production-сервере под Ubuntu?

Ответ — контейнеризация Swift приложений с Docker. Docker позволяет упаковать всё: сам бинарник, системные библиотеки, зависимости Swift Package Manager (SPM) — и запускать это где угодно. Никаких «у меня работает» и «а у меня нет».

В этой статье мы разберём, как собрать Swift-приложение в Docker-образ, оптимизировать его размер, настроить многоступенчатую сборку и даже добавить горячую перезагрузку для разработки. Поехали!

Шаг 1. Подготовка Swift-приложения

Для примера возьмём минимальное веб-приложение на Vapor. Если у вас ещё нет проекта, создайте его:

mkdir MySwiftApp && cd MySwiftApp
swift package init --type executable

Добавим Vapor в Package.swift:

// swift-tools-version:5.7
import PackageDescription

let package = Package(
    name: "MySwiftApp",
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", from: "4.89.0")
    ],
    targets: [
        .executableTarget(
            name: "MySwiftApp",
            dependencies: [
                .product(name: "Vapor", package: "vapor")
            ]
        )
    ]
)

Самый простой main.swift:

import Vapor

var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let app = Application(env)
defer { app.shutdown() }

app.get("hello") { req -> String in
    return "Hello from Swift in Docker!"
}

try app.run()

Проверьте, что проект компилируется локально: swift build. Если всё ок — переходим к контейнеризации.

Шаг 2. Пишем Dockerfile для Swift

Главный секрет хорошей контейнеризации Swift — многоступенчатая сборка. На первом этапе мы компилируем бинарник, на втором — копируем только его и рантайм-зависимости. Итоговый образ получается в 5-10 раз меньше.

# ---- Этап 1: Сборка ----
FROM swift:5.9-jammy AS build

WORKDIR /app

# Копируем Package.swift и Package.resolved для кэширования зависимостей
COPY Package.swift ./
COPY Package.resolved ./

# Скачиваем зависимости отдельно — это кэшируется Docker'ом
RUN swift package resolve

# Копируем исходный код
COPY Sources ./Sources

# Сборка в release-режиме
RUN swift build -c release --static-swift-stdlib

# ---- Этап 2: Runtime ----
FROM ubuntu:jammy AS runtime

# Устанавливаем минимальные зависимости для Swift runtime
RUN apt-get update && apt-get install -y \
    libicu70 \
    libcurl4 \
    libxml2 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Копируем только бинарник из этапа сборки
COPY --from=build /app/.build/release/MySwiftApp .

# Порт, который слушает Vapor по умолчанию
EXPOSE 8080

CMD ["./MySwiftApp"]

Ключевые моменты:

  • --static-swift-stdlib — статическая линковка стандартной библиотеки, чтобы не тащить libswiftCore.so в runtime.
  • Разделение COPY — сначала Package.swift, потом код. Так Docker использует кэш слоёв и не пересобирает зависимости при изменении исходников.
  • Базовый образ ubuntu:jammy весит ~80 МБ, итоговый образ — около 100-150 МБ.

Шаг 3. Сборка и запуск контейнера

Собираем образ:

docker build -t my-swift-app .

Запускаем:

docker run -p 8080:8080 my-swift-app

Откройте браузер: http://localhost:8080/hello. Видите приветствие? Поздравляю — контейнеризация Swift приложений с Docker выполнена!

Шаг 4. Docker Compose для разработки с горячей перезагрузкой

На этапе разработки пересобирать образ после каждой правки кода — мучительно. Используем Docker Compose с монтированием томов и командой swift run:

# docker-compose.yml
version: '3.8'

services:
  swift-app:
    image: swift:5.9-jammy
    container_name: swift-dev
    working_dir: /app
    volumes:
      - .:/app
    ports:
      - "8080:8080"
    command: swift run --skip-update
    environment:
      - SWIFT_DEVELOPMENT=true

Теперь меняйте код на хосте — контейнер автоматически перекомпилирует и перезапустит приложение. Запуск одной командой:

docker compose up

Этот подход идеален для контейнеризации Swift-приложений на этапе активной разработки.

Шаг 5. Оптимизация: уменьшаем образ до минимума

Хотите ещё меньше? Используйте scratch или alpine как базовый образ. Для Swift с статической линковкой это реально:

# Финальный этап на scratch
FROM scratch AS runtime

COPY --from=build /app/.build/release/MySwiftApp /app

EXPOSE 8080

CMD ["/app"]

Размер образа — всего 20-30 МБ! Минус: нет shell, нет apt, только бинарник. Подходит для production, где вы уверены в окружении.

Шаг 6. CI/CD и деплой контейнера

Готовый образ можно пушить в Docker Hub или GitHub Container Registry. Пример GitHub Actions workflow:

# .github/workflows/deploy.yml
name: Build and Push Swift Docker Image

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Build Docker image
        run: docker build -t ghcr.io/myorg/my-swift-app:latest .
      
      - name: Push to GitHub Packages
        run: |
          echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ghcr.io/myorg/my-swift-app:latest

После пуша образ автоматически разворачивается на сервере через docker pull. Вся контейнеризация Swift приложений становится частью пайплайна.

Частые проблемы и их решения

  • Ошибка "Segmentation fault" — обычно из-за несоответствия версий libicu. Убедитесь, что установили libicu70 (или версию, соответствующую вашей сборке

Блоги

Рекомендаций по книгам