A Swift client library for the Docker Engine API, built on top of Swift OpenAPI Generator. Communicates with the Docker daemon over a Unix socket or TCP, with full async/await support.
- Swift 6.2+
- macOS 13+ / Linux
Add SwiftDocker to your Package.swift:
dependencies: [
.package(url: "https://github.com/your-org/swift-docker", from: "1.0.0"),
],
targets: [
.target(
name: "MyTarget",
dependencies: [
.product(name: "SwiftDocker", package: "swift-docker"),
]
),
]Important: SwiftDocker uses the Swift OpenAPI Generator build plugin to generate its API client at compile time. You must build the project at least once after adding the dependency before the generated types are available in your code. In Xcode press ⌘B, or on the command line run:
swift build
With no arguments, Docker() reads the DOCKER_HOST environment variable and falls back to /var/run/docker.sock:
let docker = try Docker()Supported DOCKER_HOST formats:
| Format | Transport |
|---|---|
unix:///var/run/docker.sock |
Unix socket (Linux default) |
unix:///Users/me/.docker/run/docker.sock |
Unix socket (Docker Desktop on macOS) |
tcp://192.168.1.10:2375 |
Plain TCP |
http://192.168.1.10:2375 |
HTTP over TCP |
https://192.168.1.10:2376 |
HTTPS over TCP |
let docker = try Docker(socketPath: "/Users/me/.docker/run/docker.sock")let docker = try Docker(host: "http://192.168.1.10:2375")Supply registryAuth to authenticate with a private registry. The credentials are injected as the X-Registry-Auth header on every request:
let auth = RegistryAuth(
username: "myuser",
password: "mypassword",
serverAddress: "registry.example.com"
)
let docker = try Docker(registryAuth: auth)If you have a pre-obtained identity token (from /auth):
let token = RegistryIdentityToken(identityToken: "my-identity-token")
let docker = try Docker(registryAuth: nil) // then attach RegistryAuthMiddleware manuallyCreate an Alpine container, run a command, wait for it to finish, and read the logs:
let docker = try Docker(socketPath: "/Users/me/.docker/run/docker.sock")
// 1. Create the container
let createBody = try await docker.client.containerCreate(.init(
body: .json(.init(
value1: .init(
cmd: ["echo", "hello world"],
image: "alpine"
),
value2: .init()
))
)).created.body.json
let containerID = createBody.id
print("Created container: \(containerID)")
// 2. Start the container
_ = try await docker.client.containerStart(.init(
path: .init(id: containerID)
)).noContent
// 3. Wait for it to finish
let waitResult = try await docker.client.containerWait(.init(
path: .init(id: containerID),
query: .init(condition: .notRunning)
)).ok.body.json
print("Exited with status: \(waitResult.statusCode)")
// 4. Fetch stdout logs
let logsBody = try await docker.client.containerLogs(.init(
path: .init(id: containerID),
query: .init(stdout: true)
)).ok.body.applicationVnd_docker_multiplexedStream
let logsData = try await Data(collecting: logsBody, upTo: 1024 * 1024)
let logText = DockerMultiplexedStream.text(from: logsData)
print("Output: \(logText)") // "hello world\n"Start a long-running container and exec commands into it:
let docker = try Docker(socketPath: "/Users/me/.docker/run/docker.sock")
// 1. Create and start a long-running container
let containerID = try await docker.client.containerCreate(.init(
body: .json(.init(
value1: .init(cmd: ["sleep", "30"], image: "alpine"),
value2: .init()
))
)).created.body.json.id
_ = try await docker.client.containerStart(.init(
path: .init(id: containerID)
)).noContent
// 2. Create an exec instance
let execID = try await docker.client.containerExec(.init(
path: .init(id: containerID),
body: .json(.init(
attachStdout: true,
attachStderr: true,
cmd: ["ls", "/"]
))
)).created.body.json.id
// 3. Start it detached
_ = try await docker.client.execStart(.init(
path: .init(id: execID),
body: .json(.init(detach: true))
)).ok
// 4. Wait for the exec to finish and check its exit code
let exitCode = try await docker.waitForExec(id: execID)
print("Exit code: \(exitCode)") // 0 = success
// 5. Clean up
_ = try? await docker.client.containerStop(.init(
path: .init(id: containerID)
))Docker multiplexes stdout and stderr into a single stream when Tty: false. Use DockerMultiplexedStream to decode it:
// Collect raw multiplexed stream data
let logsBody = try await docker.client.containerLogs(.init(
path: .init(id: containerID),
query: .init(stdout: true, stderr: true)
)).ok.body.applicationVnd_docker_multiplexedStream
let rawData = try await Data(collecting: logsBody, upTo: 1024 * 1024)
// Decode into individual frames (each with a stream type and payload)
let frames = DockerMultiplexedStream.decode(rawData)
for frame in frames {
switch frame.stream {
case .stdout: print("[stdout] \(frame.text ?? "")")
case .stderr: print("[stderr] \(frame.text ?? "")")
case .stdin: break
}
}
// Or get stdout as a plain string (optionally include stderr)
let stdoutOnly = DockerMultiplexedStream.text(from: rawData)
let combined = DockerMultiplexedStream.text(from: rawData, includeStderr: true)let docker = try Docker()
let containers = try await docker.client.containerList(.init(
query: .init(all: false) // only running containers
)).ok.body.json
for container in containers {
print("\(container.id.prefix(12)) \(container.image) \(container.state)")
}let docker = try Docker()
_ = try await docker.client.imageCreate(.init(
query: .init(fromImage: "alpine", tag: "latest")
)).okThe default API version is v1.53. You can override it at init time:
let docker = try Docker(apiVersion: "v1.47")All errors thrown from the Docker initialiser conform to DockerError:
do {
let docker = try Docker(host: "not-a-valid-url")
} catch let error as DockerError {
print(error) // "Invalid Docker host URL: not-a-valid-url"
}DockerError cases:
| Case | Description |
|---|---|
.invalidBasePath(String?) |
The supplied host/socket path could not be parsed |
.valueError(String?) |
A value supplied to the library was invalid |
Docker— the main entry point. Wraps an OpenAPI-generatedClientand provides convenience helpers likewaitForExec(id:pollInterval:).UnixSocketTransport— aURLSessionTransport-compatible transport that routes requests over a POSIX Unix-domain socket. Used automatically when connecting via a socket path.DockerMultiplexedStream— decoder for Docker's multiplexed log stream format (application/vnd.docker.multiplexed-stream).RegistryAuthMiddleware— an OpenAPIClientMiddlewarethat injects theX-Registry-Authheader for private registry operations.Client— the raw OpenAPI-generated client, available asdocker.client. Every Docker Engine API endpoint is accessible directly on this object.
See LICENSE.