Paul Sitoh

I'm a software engineer who enjoys coding principally in Go and where necessary other languages.

Manipulating Docker containers via Go -- working with images

19 Jun 2021 » go, docker

About this post

In this post, I’ll show you ways to manipulate docker operations directly from your Go codes as an alternative to using Docker cli and docker-compose script.

It is beyond the scope of this post to discuss in detail Docker’s architecture. Refer to the official Docker documentation for detail.

For the purpose of this post, Docker is a client-server application where the server is running as a daemon in your platform (Linux, macOS and Windows).

In essence, when you work with your Go code (i.e. the client) to interact with Docker you are basically interacting with Docker server via sockets.

There are two ways you can get your Go code to interact with Docker server. One approach is to use Docker’s Go SDK. The other approach is to use the RESTFul API. I’ll show you how it is done using the two approaches specifically when it comes to pulling Docker images from Docker registry.

Using Go SDK

The steps involve in using the SDK are:

  • STEP 1: Instantiate a Docker client;

  • STEP 2: Use the client to call the operations you are interested in.

Here is the example code to pull down an image:

import "github.com/docker/docker/client"

// Instantiate a Docker client based on default docker environment variables
cli, _ := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())

// Call the Image pull and in this case Docker is pulling ubuntu:latest from image repository
reader, _ := cli.ImagePull(context.Background() "ubuntu", types.ImagePullOptions{})

// Stream download status to console
io.Copy(os.Stdout, reader)

Using Docker Engine API

In this approach, configure the Go Http client package to dial up docker socket. In the case of Linux and macOS, you can simply use unix socket as shown below:

func NewClient() http.Client {
	return http.Client{
		Transport: &http.Transport{
			DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
				return net.Dial("unix", "/var/run/docker.sock")
			},
		},
	}
}

For Windows programmers, please refer to the operating system’s equivalent.

Next, connect your client to the Docker API. The API to pull images is in the endpoint /images/create. Yes, pull and create images have the same endpoint. So it can be confusing.

Here is the example code demonstrating operations to call the API.

func PullImages(client http.Client, imageName string, tag string) (io.ReadCloser, error) {

	queryParams := fmt.Sprintf("?fromImage=%s&fromSrc=-&tag=%s", imageName, tag)
	res, err := client.Post(fmt.Sprintf("http://v1.40/images/create%s", queryParams), "application/json", nil)
	if err != nil {
		return nil, err
	}
	return res.Body, nil
}

Last words

As you can see the steps involved in creating a Go base client using SDK and API is fairly straightforward.

However, you might be asking why not just execute shell commands via Docker’s cli with Docker Compose script. Of course, you could. What I have presented here are illustrations of alternative ways to manipulate docker from Go code. It is for you to find a use case appropriate for your project. For example, you might find it useful to create native apps manipulating a few Docker containers where you might not need platforms as complex as Kubernetes to manipulate containers.

In this post I have covered one operation, which is to pull an image. The steps involved in manipulating other operations is broadly the same except for details such as SDK arguments or API parameters settings. I’ll cover those and other Docker components in future posts.