Introduction: Why Does a Swift Developer Need Docker?
Many Swift developers (especially in the iOS and macOS world) are accustomed to Xcode, simulators, and manual dependency management. However, with the growing popularity of server-side Swift (for example, the Vapor framework) and the need for test automation and deployment, containerization is becoming an essential skill.
Docker allows you to package your Swift application along with all its dependencies into an isolated container. This ensures that the code will work identically on your local machine, on a CI/CD server, and in production. In this article, we will break down how to create a Docker image for a Swift project, optimize its size, and run it in a container.
1. Docker Basics for Swift: Dockerfile and Multi-stage Build
The standard approach to containerizing Swift applications is using a multi-stage build. This allows you to separate the compilation stage (where heavy Swift tools are needed) from the execution stage (where only the compiled binary is sufficient).
Consider a typical Swift project, for example, a Vapor server. Let's create a Dockerfile in the project root:
# First stage: buildFROM swift:5.9-focal AS build
WORKDIR /app
# Copy Package.swift and Package.resolved files to cache dependenciesCOPY Package.swift Package.resolved ./RUN swift package resolve
# Copy the source codeCOPY . .
# Build in release modeRUN swift build -c release --static-swift-stdlib
# Second stage: minimal image for executionFROM ubuntu:focal
WORKDIR /app
# Copy the built binary from the first stageCOPY --from=build /app/.build/release/Run .
# Expose the port (Vapor uses 8080 by default)EXPOSE 8080
# Run the applicationCMD ["./Run"]Important points:
--static-swift-stdlib— static linking of the Swift standard library reduces dependencies at runtime.- Copying
Package.swiftbefore the source code is a trick for caching Docker dependency layers. - The base image
ubuntu:focalis lightweight but contains all necessary system libraries.
2. Building and Running the Container
Now that the Dockerfile is ready, let's build the image and run the container:
# Build the imagedocker build -t my-swift-app .
# Run the container in the background with port forwardingdocker run -d -p 8080:8080 --name swift-container my-swift-app
# Check the logsdocker logs swift-container
# Test (if the application is an HTTP server)curl http://localhost:8080/If you are using Swift Package Manager without Vapor, the binary might have a different name. In that case, change the path in COPY --from=build and CMD. You can find the exact binary name with the command:
swift build -c release --show-bin-path2.1. Working with .dockerignore
To avoid copying unnecessary files into the build context (e.g., .git, build/), create a .dockerignore file:
.git.gitignore.build/*.xcodeproj*.xcworkspace.DS_StoreREADME.md3. Docker Compose for Swift Applications
If your project uses a database (PostgreSQL, Redis) or multiple microservices, it is convenient to use docker-compose.yml. Example for Vapor + PostgreSQL:
version: '3.8'
services: app: build: . ports: - "8080:8080" environment: - DATABASE_HOST=db - DATABASE_PORT=5432 - DATABASE_NAME=vapor_db - DATABASE_USER=vapor_user - DATABASE_PASSWORD=secret depends_on: - db
db: image: postgres:15 environment: POSTGRES_DB: vapor_db POSTGRES_USER: vapor_user POSTGRES_PASSWORD: secret ports: