DockerDocker Engine API

Introduction

An API that allows you to control every aspect of Docker from within your own applications, build tools to manage and monitor applications running on Docker, and even use it to build apps on Docker itself.

Enable Remote access to Docker API on Linux

Edit /etc/init/docker.conf and update the DOCKER_OPTS variable to the following:

DOCKER_OPTS='-H tcp://0.0.0.0:4243 -H unix:///var/run/docker.sock'

Restart Docker deamon

service docker restart

Verify if Remote API is working

curl -X GET http://localhost:4243/images/json

Enable Remote access to Docker API on Linux running systemd

Linux running systemd, like Ubuntu 16.04, adding -H tcp://0.0.0.0:2375 to /etc/default/docker does not have the effect it used to.

Instead, create a file called /etc/systemd/system/docker-tcp.socket to make docker available on a TCP socket on port 4243:

[Unit]
Description=Docker Socket for the API  
[Socket]
ListenStream=4243  
Service=docker.service  
[Install]
WantedBy=sockets.target 

Then enable the new socket:

systemctl enable docker-tcp.socket
systemctl enable docker.socket
systemctl stop docker
systemctl start docker-tcp.socket
systemctl start docker

Now, verify if Remote API is working:

curl -X GET http://localhost:4243/images/json

Enable Remote Access with TLS on Systemd

Copy the package installer unit file to /etc where changes will not be overwritten on an upgrade:

cp /lib/systemd/system/docker.service /etc/systemd/system/docker.service

Update /etc/systemd/system/docker.service with your options on ExecStart:

ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376 \
  --tlsverify --tlscacert=/etc/docker/certs/ca.pem \
  --tlskey=/etc/docker/certs/key.pem \
  --tlscert=/etc/docker/certs/cert.pem

Note that dockerd is the 1.12 daemon name, prior it was docker daemon. Also note that 2376 is dockers standard TLS port, 2375 is the standard unencrypted port. See this page for steps to create your own TLS self signed CA, cert, and key.

After making changes to the systemd unit files, run the following to reload the systemd config:

systemctl daemon-reload

And then run the following to restart docker:

systemctl restart docker

It's a bad idea to skip TLS encryption when exposing the Docker port since anyone with network access to this port effectively has full root access on the host.

Image pulling with progress bars, written in Go

Here is an example of image pulling using Go and Docker Engine API and the same progress bars as the ones shown when you run docker pull your_image_name in the CLI. For the purposes of the progress bars are used some ANSI codes.

package yourpackage

import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "strings"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/client"
)

// Struct representing events returned from image pulling
type pullEvent struct {
    ID             string `json:"id"`
    Status         string `json:"status"`
    Error          string `json:"error,omitempty"`
    Progress       string `json:"progress,omitempty"`
    ProgressDetail struct {
        Current int `json:"current"`
        Total   int `json:"total"`
    } `json:"progressDetail"`
}

// Actual image pulling function
func PullImage(dockerImageName string) bool {
    client, err := client.NewEnvClient()

    if err != nil {
        panic(err)
    }

    resp, err := client.ImagePull(context.Background(), dockerImageName, types.ImagePullOptions{})

    if err != nil {
        panic(err)
    }

    cursor := Cursor{}
    layers := make([]string, 0)
    oldIndex := len(layers)

    var event *pullEvent
    decoder := json.NewDecoder(resp)

    fmt.Printf("\n")
    cursor.hide()

    for {
        if err := decoder.Decode(&event); err != nil {
            if err == io.EOF {
                break
            }

            panic(err)
        }

        imageID := event.ID

        // Check if the line is one of the final two ones
        if strings.HasPrefix(event.Status, "Digest:") || strings.HasPrefix(event.Status, "Status:") {
            fmt.Printf("%s\n", event.Status)
            continue
        }

        // Check if ID has already passed once
        index := 0
        for i, v := range layers {
            if v == imageID {
                index = i + 1
                break
            }
        }

        // Move the cursor
        if index > 0 {
            diff := index - oldIndex

            if diff > 1 {
                down := diff - 1
                cursor.moveDown(down)
            } else if diff < 1 {
                up := diff*(-1) + 1
                cursor.moveUp(up)
            }

            oldIndex = index
        } else {
            layers = append(layers, event.ID)
            diff := len(layers) - oldIndex

            if diff > 1 {
                cursor.moveDown(diff) // Return to the last row
            }

            oldIndex = len(layers)
        }

        cursor.clearLine()

        if event.Status == "Pull complete" {
            fmt.Printf("%s: %s\n", event.ID, event.Status)
        } else {
            fmt.Printf("%s: %s %s\n", event.ID, event.Status, event.Progress)
        }

    }

    cursor.show()

    if strings.Contains(event.Status, fmt.Sprintf("Downloaded newer image for %s", dockerImageName)) {
        return true
    }

    return false
}

For better readability, cursor actions with the ANSI codes are moved to a separate structure, which looks like this:

package yourpackage

import "fmt"

// Cursor structure that implements some methods
// for manipulating command line's cursor
type Cursor struct{}

func (cursor *Cursor) hide() {
    fmt.Printf("\033[?25l")
}

func (cursor *Cursor) show() {
    fmt.Printf("\033[?25h")
}

func (cursor *Cursor) moveUp(rows int) {
    fmt.Printf("\033[%dF", rows)
}

func (cursor *Cursor) moveDown(rows int) {
    fmt.Printf("\033[%dE", rows)
}

func (cursor *Cursor) clearLine() {
    fmt.Printf("\033[2K")
}

After that in your main package you can call the PullImage function passing the image name you want to pull. Of course, before calling it, you have to be logged into the Docker registry, where the image is.

Making a cURL request with passing some complex structure

When using cURL for some queries to the Docker API, it might be a bit tricky to pass some complex structures. Let's say, getting a list of images allows using filters as a query parameter, which have to be a JSON representation of map[string][]string (about the maps in Go you can find more here).
Here is how to achieve this:

curl --unix-socket /var/run/docker.sock \
    -XGET "http:/v1.29/images/json" \
    -G \
    --data-urlencode 'filters={"reference":{"yourpreciousregistry.com/path/to/image": true}, "dangling":{"true": true}}'

Here the -G flag is used to specify that the data in the --data-urlencode parameter will be used in an HTTP GET request instead of the POST request that otherwise would be used. The data will be appended to the URL with a ? separator.