Build and Push Docker Images with Go

Guide on how to build and push Docker images programmatically using Go.
profile
Andy YeungFirst published: 2020-12-08Last updated: 2025-06-25
build-push-docker-images-golang

Let's walk through how to build and push Docker images programmatically using Go. To do this, we need to talk to the Docker daemon via the Docker Engine API. This is similar to how the Docker CLI works, but instead of entering commands through a CLI, we'll be writing code with Docker's Go SDK.

At the time of writing, the official Docker Go SDK docs provide great examples of running basic Docker commands with Go. However, it's missing examples on building and pushing Docker images, so we'll go over those in this blog.

Before we begin, this blog assumes you have a working knowledge of Docker and Go. We'll go over the following:

  • Building an image from local source code
  • Pushing an image to a remote registry

Environment Setup

First, we need to set up the environment. Create a project and include the app we want to containerize:

1mkdir docker-go-tutorial && cd docker-go-tutorial && mkdir node-hello

We'll add a simple Node.js app:

1// node-hello/app.js
2console.log("Hello From LoginRadius");

with the Dockerfile:

1// node-hello/Dockerfile
2FROM node:12
3WORKDIR /src
4COPY . .
5CMD [ "node", "app.js" ]

Next, install the Go SDK. These are the Docker related imports we will be using:

1"github.com/docker/docker/api/types"
2"github.com/docker/docker/client"
3"github.com/docker/docker/pkg/archive"

Build Docker Image

One way to build a Docker image from our local files is to compress those files into a tar archive first.

We use the archive package provided by Docker:

1"github.com/docker/docker/pkg/archive"
1tar, err := archive.TarWithOptions("node-hello/", &archive.TarOptions{})
2if err != nil {
3    return err
4}

Now, we can call the ImageBuild function using the Go SDK:

  • Note that the image tag includes our Docker registry user ID, so we can push this image to our registry later.
1opts := types.ImageBuildOptions{
2    Dockerfile:  "Dockerfile",
3    Tags:        []string{dockerRegistryUserID + "/node-hello"},
4    Remove:      true,
5}
6res, err := dockerClient.ImageBuild(ctx, tar, opts)
7if err != nil {
8    return err
9}

To print the response, we use a scanner to go through line by line:

1scanner := bufio.NewScanner(res.Body)
2for scanner.Scan() {
3    lastLine = scanner.Text()
4    fmt.Println(scanner.Text())
5}

This prints the following:

1{"stream":"Step 1/4 : FROM node:12"}
2{"stream":"\n"}
3{"stream":" ---\u003e e4f1e16b3633\n"}
4{"stream":"Step 2/4 : WORKDIR /src"}
5{"stream":"\n"}
6{"stream":" ---\u003e Using cache\n"}
7{"stream":" ---\u003e b298b8519669\n"}
8{"stream":"Step 3/4 : COPY . ."}
9{"stream":"\n"}
10{"stream":" ---\u003e Using cache\n"}
11{"stream":" ---\u003e 1ff6a87e79d9\n"}
12{"stream":"Step 4/4 : CMD [ \"node\", \"app.js\" ]"}
13{"stream":"\n"}
14{"stream":" ---\u003e Using cache\n"}
15{"stream":" ---\u003e 6ca44f72b68d\n"}
16{"aux":{"ID":"sha256:238a923459uf28h80103eb089804a2ff2c1f68f8c"}}
17{"stream":"Successfully built 6ca44f72b68d\n"}
18{"stream":"Successfully tagged lrblake/node-hello:latest\n"}

The last step would be checking the response for errors, so if something went wrong during the build, we could handle it.

1errLine := &ErrorLine{}
2json.Unmarshal([]byte(lastLine), errLine)
3if errLine.Error != "" {
4    return errors.New(errLine.Error)
5}

For example, the following error can occur during build:

1{"errorDetail":{"message":"COPY failed: stat /var/lib/docker/tmp/docker-builder887191115/z: no such file or directory"},"error":"COPY failed: stat /var/lib/docker/tmp/docker-builder887191115/z: no such file or directory"}

All together, the file looks like this:

1package main
2import (
3"bufio"
4"context"
5"encoding/json"
6"errors"
7"fmt"
8"io"
9"time"
10"github.com/docker/docker/api/types"
11"github.com/docker/docker/client"
12"github.com/docker/docker/pkg/archive"
13
14)
15var dockerRegistryUserID = ""
16type ErrorLine struct {
17Error       string      json:"error"
18ErrorDetail ErrorDetail json:"errorDetail"
19}
20type ErrorDetail struct {
21Message string json:"message"
22}
23func main() {
24cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
25if err != nil {
26fmt.Println(err.Error())
27return
28}
29err = imageBuild(cli)
30if err != nil {
31	fmt.Println(err.Error())
32	return
33}
34
35}
36func imageBuild(dockerClient client.Client) error {
37ctx, cancel := context.WithTimeout(context.Background(), time.Second120)
38defer cancel()
39tar, err := archive.TarWithOptions("node-hello/", &archive.TarOptions{})
40if err != nil {
41	return err
42}
43
44opts := types.ImageBuildOptions{
45	Dockerfile: "Dockerfile",
46	Tags:       []string{dockerRegistryUserID + "/node-hello"},
47	Remove:     true,
48}
49res, err := dockerClient.ImageBuild(ctx, tar, opts)
50if err != nil {
51	return err
52}
53
54defer res.Body.Close()
55
56err = print(res.Body)
57if err != nil {
58	return err
59}
60
61return nil
62
63}
64func print(rd io.Reader) error {
65var lastLine string
66scanner := bufio.NewScanner(rd)
67for scanner.Scan() {
68	lastLine = scanner.Text()
69	fmt.Println(scanner.Text())
70}
71
72errLine := &ErrorLine{}
73json.Unmarshal([]byte(lastLine), errLine)
74if errLine.Error != "" {
75	return errors.New(errLine.Error)
76}
77
78if err := scanner.Err(); err != nil {
79	return err
80}
81
82return nil
83
84}

The equivalent Docker CLI command would be:

1docker build -t <dockerRegistryUserID>/node-hello .

Push Docker Image

We'll push the Docker image we created to Docker Hub. But, we need to authenticate with Docker Hub by providing credentials encoded in base64.

  • In practice, don't hardcode your credentials in your source code.
  • If you don't want to use your Docker Hub password, you can set up an access token and provide that in the Password field instead.
1var authConfig = types.AuthConfig{
2	Username:      "Your Docker Hub Username",
3	Password:      "Your Docker Hub Password or Access Token",
4	ServerAddress: "https://index.docker.io/v1/",
5}
6authConfigBytes, _ := json.Marshal(authConfig)
7authConfigEncoded := base64.URLEncoding.EncodeToString(authConfigBytes)

Now, call the ImagePush function in the Go SDK, along with your encoded credentials:

1opts := types.ImagePushOptions{RegistryAuth: authConfigEncoded}
2rd, err := dockerClient.ImagePush(ctx, dockerRegistryUserID + "/node-hello", opts)

Together, this looks like:

1func imagePush(dockerClient *client.Client) error {
2	ctx, cancel := context.WithTimeout(context.Background(), time.Second*120)
3	defer cancel()
4authConfigBytes, _ := json.Marshal(authConfig)
5authConfigEncoded := base64.URLEncoding.EncodeToString(authConfigBytes)
6
7tag := dockerRegistryUserID + "/node-hello"
8opts := types.ImagePushOptions{RegistryAuth: authConfigEncoded}
9rd, err := dockerClient.ImagePush(ctx, tag, opts)
10if err != nil {
11	return err
12}
13
14defer rd.Close()
15
16err = print(rd)
17if err != nil {
18	return err
19}
20
21return nil
22
23}

The equivalent Docker CLI command (after docker login) would be:

1docker push <dockerRegistryUserID>/node-hello
Share On:
Share on TwitterShare on LinkedIn