This is a low-level Docker Client written in Swift. It very closely follows the Docker API.
It fully uses the Swift concurrency features introduced with Swift 5.5 (async/await).
Docker API version support
This client library aims at implementing the Docker API version 1.41 (https://docs.docker.com/engine/api/v1.41).
This means that it will work with Docker >= 20.10.
Current implementation status
Section
Operation
Support
Notes
Client connection
Local Unix socket
✅
HTTP
✅
HTTPS
✅
Docker daemon & System info
Ping
✅
Info
✅
Version
✅
Events
✅
Get data usage info
✅
Containers
List
✅
Inspect
✅
Create
✅
Update
✅
Rename
✅
Start/Stop/Kill
✅
Pause/Unpause
✅
Get logs
✅
Get stats
✅
Get processes (top)
✅
Delete
✅
Prune
✅
Wait
✅
Filesystem changes
✅
untested
Attach
✅
basic support 1
Exec
❌
unlikely 2
Resize TTY
❌
Images
List
✅
Inspect
✅
History
✅
Pull
✅
basic support
Build
✅
basic support
Tag
✅
Push
✅
Create (container commit)
✅
Delete
✅
Prune
✅
Swarm
Init
✅
Join
✅
Inspect
✅
Leave
✅
Update
✅
Nodes
List
✅
Inspect
✅
Update
✅
Delete
✅
Services
List
✅
Inspect
✅
Create
✅
Get logs
✅
Update
✅
Rollback
✅
Delete
✅
Networks
List
✅
Inspect
✅
Create
✅
Delete
✅
Prune
✅
(Dis)connect container
✅
Volumes
List
✅
Inspect
✅
Create
✅
Delete
✅
Prune
✅
Secrets
List
✅
Inspect
✅
Create
✅
Update
✅
Delete
✅
Configs
List
✅
Inspect
✅
Create
✅
Update
✅
Delete
✅
Tasks
List
✅
Inspect
✅
Get logs
✅
Plugins
List
✅
Inspect
✅
Get Privileges
✅
Install
✅
Remove
✅
Enable/disable
✅
Upgrade
✅
untested
Configure
✅
untested
Create
❌
TBD
Push
❌
TBD
Registries
Login
✅
basic support
Docker error responses mgmt
🚧
✅ : done or mostly done
🚧 : work in progress, partially implemented, might not work
❌ : not implemented/supported at the moment.
Note: various Docker endpoints such as list or prune support filters. These are currently not implemented.
1 Attach is currently not supported when connecting to Docker via local Unix socket, or when using a proxy. It uses the Websocket protocol.
2 Docker exec is using an unconventional protocol that requires raw access to the TCP socket. Significant work needed in order to support it.
To add DockerClientSwift to your existing Xcode project, select File -> Swift Packages -> Add Package Dependancy.
Enter https://github.com/m-barthelemy/DockerSwift.git for the URL.
Usage Examples
Connect to a Docker daemon
Local socket (defaults to /var/run/docker.sock):
import DockerSwift
let docker = DockerClient()
defer {try! docker.syncShutdown()}
Now, we should get an event whose action is “create” and whose type is “container”.
for try await event in try await events {
print("\n••• event: \(event)")
}
Containers
List containers
Add all: true to also return stopped containers.
let containers = try await docker.containers.list()
Get a container details
let container = try await docker.containers.get("nameOrId")
Create a container
Note: you will also need to start it for the container to actually run.
The simplest way of creating a new container is to only specify the image to run:
let spec = ContainerSpec(
config: .init(image: "hello-world:latest")
)
let container = try await docker.containers.create(name: "test", spec: spec)
Docker allows customizing many parameters:
let spec = ContainerSpec(
config: .init(
// Override the default command of the Image
command: ["/custom/command", "--option"],
// Add new environment variables
environmentVars: ["HELLO=hi"],
// Expose port 80
exposedPorts: [.tcp(80)],
image: "nginx:latest",
// Set custom container labels
labels: ["label1": "value1", "label2": "value2"]
),
hostConfig: .init(
// Memory the container is allocated when starting
memoryReservation: .mb(64),
// Maximum memory the container can use
memoryLimit: .mb(128),
// Needs to be either disabled (-1) or be equal to, or greater than, `memoryLimit`
memorySwap: .mb(128),
// Let's publish the port we exposed in `config`
portBindings: [.tcp(80): [.publishTo(hostIp: "0.0.0.0", hostPort: 8000)]]
)
)
let container = try await docker.containers.create(name: "nginx-test", spec: spec)
Update a container
Let’s update the memory limits for an existing container:
try await docker.containers.rename("nameOrId", to: "hahi")
Delete a container
If the container is running, deletion can be forced by passing force: true
try await docker.containers.remove("nameOrId")
Get container logs
Logs are streamed progressively in an asynchronous way.
Get all logs:
let container = try await docker.containers.get("nameOrId")
for try await line in try await docker.containers.logs(container: container, timestamps: true) {
print(line.message + "\n")
}
Wait for future log messages:
let container = try await docker.containers.get("nameOrId")
for try await line in try await docker.containers.logs(container: container, follow: true) {
print(line.message + "\n")
}
Only the last 100 messages:
let container = try await docker.containers.get("nameOrId")
for try await line in try await docker.containers.logs(container: container, tail: 100) {
print(line.message + "\n")
}
Attach to a container
Let’s create a container that defaults to running a shell, and attach to it:
let _ = try await docker.images.pull(byIdentifier: "alpine:latest")
let spec = ContainerSpec(
config: .init(
attachStdin: true,
attachStdout: true,
attachStderr: true,
image: "alpine:latest",
openStdin: true
)
)
let container = try await docker.containers.create(spec: spec)
let attach = try await docker.containers.attach(container: container, stream: true, logs: true)
// Let's display any output from the container
Task {
for try await output in attach.output {
print("• \(output)")
}
}
// We need to be sure that the container is really running before being able to send commands to it.
try await docker.containers.start(container.id)
try await Task.sleep(nanoseconds: 1_000_000_000)
// Now let's send the command; the response will be printed to the screen.
try await attach.send("uname")
Images
List the Docker images
let images = try await docker.images.list()
Get an image details
let image = try await docker.images.get("nameOrId")
Pull an image
Pull an image from a public repository:
let image = try await docker.images.pull(byIdentifier: "hello-world:latest")
Pull an image from a registry that requires authentication:
var credentials = RegistryAuth(username: "myUsername", password: "....")
try await docker.registries.login(credentials: &credentials)
let image = try await docker.images.pull(byIdentifier: "my-private-image:latest", credentials: credentials)
NOTE: RegistryAuth also accepts a serverAddress parameter in order to use a custom registry.
Creating images from a remote URL or from the standard input is currently not supported.
Push an image
Supposing that the Docker daemon has an image named “my-private-image:latest”:
NOTE: RegistryAuth also accepts a serverAddress parameter in order to use a custom registry.
Build an image
The current implementation of this library is very bare-bones.
The Docker build context, containing the Dockerfile and any other resources required during the build, must be passed as a TAR archive.
Supposing we already have a TAR archive of the build context:
let tar = FileManager.default.contents(atPath: "/tmp/docker-build.tar")
let buffer = ByteBuffer.init(data: tar)
let buildOutput = try await docker.images.build(
config: .init(dockerfile: "./Dockerfile", repoTags: ["build:test"]),
context: buffer
)
// The built Image ID is returned towards the end of the build output
var imageId: String!
for try await item in buildOutput {
if item.aux != nil {
imageId = item.aux!.id
}
else {
print("\n• Build output: \(item.stream)")
}
}
print("\n• Image ID: \(imageId)")
You can use external libraries to create TAR archives of your build context.
Example with Tarscape (only available on macOS):
The client must be connected to a Swarm manager node.
let swarm = try await docker.swarm.get()
Make the Docker daemon to join an existing Swarm cluster
// This first client points to an existing Swarm cluster manager
let swarmClient = Dockerclient(...)
let swarm = try await swarmClient.swarm.get()
// This client is the docker daemon we want to add to the Swarm cluster
let client = Dockerclient(...)
try await client.swarm.join(
config: .init(
// To join the Swarm cluster as a Manager node
joinToken: swarmClient.joinTokens.manager,
// IP/Host of the existing Swarm managers
remoteAddrs: ["10.0.0.1"]
)
)
Remove the current Node from the Swarm
Note: force is needed if the node is a manager
try await docker.swarm.leave(force: true)
Nodes
This requires a Docker daemon with Swarm mode enabled.
Additionally, the client must be connected to a manager node.
What if we then want to know when our service is fully running?
var index = 0 // Keep track of how long we've been waiting
repeat {
try await Task.sleep(nanoseconds: 1_000_000_000)
print("\n Service still not fully running!")
index += 1
} while try await docker.tasks.list()
.filter({$0.serviceId == service.id && $0.status.state == .running})
.count < 1 /* number of replicas */ && index < 15
print("\n Service is fully running!")
What if we want to create a one-off job instead of a service?
storing data into a custom Volume, for each container
requiring a Secret
publishing the port 80 of the containers to the port 8000 of each Docker Swarm node
getting restarted automatically in case of failure
```swift
let network = try await docker.networks.create(spec: .init(name: “myNet”, driver: “overlay”))
let secret = try await docker.secrets.create(spec: .init(name: “myPassword”, value: “blublublu”))
let spec = ServiceSpec(
name: “my-nginx”,
taskTemplate: .init(
containerSpec: .init(
image: "nginx:latest",
// Create and mount a dedicated Volume named "myStorage" on each running container.
mounts: [.volume(name: "myVolume", to: "/mnt")],
// Add our Secret. Will appear as `/run/secrets/myPassword` in the containers.
secrets: [.init(secret)]
),
resources: .init(
limits: .init(memoryBytes: .mb(64))
),
// If a container exits or crashes, replace it with a new one.
restartPolicy: .init(condition: .any, delay: .seconds(2), maxAttempts: 2)
),
mode: .replicated(1),
// Add our custom Network
networks: [.init(target: network.id)],
// Publish our Nginx image port 80 to 8000 on the Docker Swarm nodes
endpointSpec: .init(ports: [.init(name: “HTTP”, targetPort: 80, publishedPort: 8000)])
)
let service = try await docker.services.create(spec: spec)
</details>
<details>
<summary>Update a service</summary>
Let's scale an existing service up to 3 replicas:
```swift
let service = try await docker.services.get("nameOrId")
var updatedSpec = service.spec
updatedSpec.mode = .replicated(3)
try await docker.services.update("nameOrId", spec: updatedSpec)
Get service logs
Logs are streamed progressively in an asynchronous way.
Get all logs:
let service = try await docker.services.get("nameOrId")
for try await line in try await docker.services.logs(service: service) {
print(line.message + "\n")
}
Wait for future log messages:
let service = try await docker.services.get("nameOrId")
for try await line in try await docker.services.logs(service: service, follow: true) {
print(line.message + "\n")
}
Only the last 100 messages:
let service = try await docker.services.get("nameOrId")
for try await line in try await docker.services.logs(service: service, tail: 100) {
print(line.message + "\n")
}
Rollback a service
Suppose that we updated our existing service configuration, and something is not working properly.
We want to revert back to the previous, working version.
try await docker.services.rollback("nameOrId")
Delete a service
try await docker.services.remove("nameOrId")
Secrets
This requires a Docker daemon with Swarm mode enabled.
Note: The API for managing Docker Configs is very similar to the Secrets API and the below examples also apply to them.
List secrets
let secrets = try await docker.secrets.list()
Get a secret details
Note: The Docker API doesn’t return secret data/values.
let secret = try await docker.secrets.get("nameOrId")
Create a secret
Create a Secret containing a String value:
let secret = try await docker.secrets.create(
spec: .init(name: "mySecret", value: "test secret value 💥")
)
You can also pass a Data value to be stored as a Secret:
let data: Data = ...
let secret = try await docker.secrets.create(
spec: .init(name: "mySecret", data: data)
)
Update a secret
Currently, only the labels field can be updated (Docker limitation).
Note: the install() method can be passed a credentials parameter containing credentials for a private registry.
See “Pull an image” for more information.
// First, we fetch the privileges required by the plugin:
let privileges = try await docker.plugins.getPrivileges("vieux/sshfs:latest")
// Now, we can install it
try await docker.plugins.install(remote: "vieux/sshfs:latest", privileges: privileges)
// finally, we need to enable it before using it
try await docker.plugins.enable("vieux/sshfs:latest")
This project is released under the MIT license. See LICENSE for details.
Contribute
You can contribute to this project by submitting a detailed issue or by forking this project and sending a pull request. Contributions of any kind are very welcome :)
Docker Client
This is a low-level Docker Client written in Swift. It very closely follows the Docker API.
It fully uses the Swift concurrency features introduced with Swift 5.5 (
async
/await
).Docker API version support
This client library aims at implementing the Docker API version 1.41 (https://docs.docker.com/engine/api/v1.41). This means that it will work with Docker >= 20.10.
Current implementation status
✅ : done or mostly done
🚧 : work in progress, partially implemented, might not work
❌ : not implemented/supported at the moment.
Note: various Docker endpoints such as list or prune support filters. These are currently not implemented.
1 Attach is currently not supported when connecting to Docker via local Unix socket, or when using a proxy. It uses the Websocket protocol.
2 Docker exec is using an unconventional protocol that requires raw access to the TCP socket. Significant work needed in order to support it.
Installation
Package.swift
Xcode Project
To add DockerClientSwift to your existing Xcode project, select File -> Swift Packages -> Add Package Dependancy. Enter
https://github.com/m-barthelemy/DockerSwift.git
for the URL.Usage Examples
Connect to a Docker daemon
Local socket (defaults to
/var/run/docker.sock
):Remote daemon over HTTP:
Remote daemon over HTTPS, using a client certificate for authentication:
Docker system info
Get detailed information about the Docker daemon
Get versions information about the Docker daemon
Listen for Docker daemon events
We start by listening for docker events, then we create a container:
Now, we should get an event whose
action
is “create” and whosetype
is “container”.Containers
List containers
Add
all: true
to also return stopped containers.Get a container details
Create a container
The simplest way of creating a new container is to only specify the image to run:
Docker allows customizing many parameters:
Update a container
Let’s update the memory limits for an existing container:
Start a container
Stop a container
Rename a container
Delete a container
If the container is running, deletion can be forced by passing
force: true
Get container logs
Get all logs:
Wait for future log messages:
Only the last 100 messages:
Attach to a container
Let’s create a container that defaults to running a shell, and attach to it:
Images
List the Docker images
Get an image details
Pull an image
Pull an image from a public repository:
Pull an image from a registry that requires authentication:
Push an image
Supposing that the Docker daemon has an image named “my-private-image:latest”:
Build an image
Supposing we already have a TAR archive of the build context:
You can use external libraries to create TAR archives of your build context. Example with Tarscape (only available on macOS):
Networks
List networks
Get a network details
Create a network
Create a new network without any custom options:
Create a new network with custom IPs range:
Delete a network
Connect an existing Container to a Network
Volumes
List volumes
Get a volume details
Create a volume
Delete a volume
Swarm
Initialize Swarm mode
Get Swarm cluster details (inspect)
Make the Docker daemon to join an existing Swarm cluster
Remove the current Node from the Swarm
Nodes
List the Swarm nodes
Remove a Node from a Swarm
Services
List services
Get a service details
Create a service
Simplest possible example, we only specify the name of the service and the image to use:
Let’s specify a number of replicas, a published port and a memory limit of 64MB for our service:
What if we then want to know when our service is fully running?
What if we want to create a one-off job instead of a service?
Something more advanced? Let’s create a Service:
let service = try await docker.services.create(spec: spec)
Get service logs
Get all logs:
Wait for future log messages:
Only the last 100 messages:
Rollback a service
Suppose that we updated our existing service configuration, and something is not working properly. We want to revert back to the previous, working version.
Delete a service
Secrets
List secrets
Get a secret details
Create a secret
Create a Secret containing a
String
value:You can also pass a
Data
value to be stored as a Secret:Update a secret
Delete a secret
Plugins
List installed plugins
Install a plugin
Credits
This is a fork of the great work at https://github.com/alexsteinerde/docker-client-swift
License
This project is released under the MIT license. See LICENSE for details.
Contribute
You can contribute to this project by submitting a detailed issue or by forking this project and sending a pull request. Contributions of any kind are very welcome :)