使用 Go 客户端发送 Docker 上下文为 Tar 找不到 Docker 文件

Send Docker Context as Tar with Go Client can't find Dockerfile

我正在使用 Go Docker Client 尝试从 Dockerfile 构建图像,其内容在代码中定义。

根据 Docker Daemon API Documentation

The input stream must be a tar archive...

...The archive must include a build instructions file, typically called Dockerfile at the archive’s root.

所以我想在代码中创建构建上下文,将其写入 tar 文件,然后将其发送到要构建的 Docker 守护进程。为此,我可以使用 ImageBuild function 并将 tar 文件(构建上下文)作为 io.ReadCloser 传递。只要我的 Dockerfile 位于该压缩存档的根目录下,它就应该找到并构建它。

但是,我得到了常见错误:

Error response from daemon: Cannot locate specified Dockerfile: Dockerfile

这显然意味着它无法在存档的根目录下找到 Docker 文件。我不确定为什么。我相信我这样做的方式会在 tar 存档的根目录中添加一个 Dockerfile。守护进程应该看到这个。我在这里误解了什么?

要重现的代码片段

    var buf bytes.Buffer
    tarWriter := tar.NewWriter(&buf)

    contents := "FROM alpine\nCMD [\"echo\", \"this is from the archive\"]"

    if err := tarWriter.WriteHeader(&tar.Header{
        Name:     "Dockerfile",
        Mode:     777,
        Size:     int64(len(contents)),
        Typeflag: tar.TypeReg,
    }); err != nil {
        panic(err)
    }

    if _, err := tarWriter.Write([]byte(contents)); err != nil {
        panic(err)
    }

    if err := tarWriter.Close(); err != nil {
        panic(err)
    }

    reader := tar.NewReader(&buf)
    c, err := client.NewEnvClient()
    if err != nil {
        panic(err)
    }

    _, err = c.ImageBuild(context.Background(), reader, types.ImageBuildOptions{
        Context:    reader,
        Dockerfile: "Dockerfile",
    })
    if err != nil {
        panic(err)
    }

go.mod 文件

module docker-tar

go 1.12

require (
    github.com/docker/distribution v2.7.1+incompatible // indirect
    github.com/docker/docker v1.13.1
    github.com/docker/go-connections v0.4.0 // indirect
    github.com/docker/go-units v0.4.0 // indirect
    github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
    github.com/pkg/errors v0.8.1 // indirect
    golang.org/x/net v0.0.0-20191112182307-2180aed22343 // indirect
)
  1. 777 前加一个零表示八进制:07770o7770O777

  2. 使用reader := bytes.NewReader(buf.Bytes())而不是tar.NewReader(&buf)

  3. 对较新的版本也使用 client.WithAPIVersionNegotiation()

  4. 试试这个工作版本:

package main

import (
    "archive/tar"
    "bytes"
    "context"
    "fmt"

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

func main() {
    var buf bytes.Buffer
    tarWriter := tar.NewWriter(&buf)
    contents := `FROM alpine:3.10.3
CMD ["echo", "this is from the archive"]
`
    header := &tar.Header{
        Name:     "Dockerfile",
        Mode:     0o777,
        Size:     int64(len(contents)),
        Typeflag: tar.TypeReg,
    }
    err := tarWriter.WriteHeader(header)
    if err != nil {
        panic(err)
    }
    _, err = tarWriter.Write([]byte(contents))
    if err != nil {
        panic(err)
    }
    err = tarWriter.Close()
    if err != nil {
        panic(err)
    }

    c, err := client.NewClientWithOpts(client.WithAPIVersionNegotiation())
    if err != nil {
        panic(err)
    }
    fmt.Println(c.ClientVersion())

    reader := bytes.NewReader(buf.Bytes()) // tar.NewReader(&buf)
    ctx := context.Background()
    buildOptions := types.ImageBuildOptions{
        Context:    reader,
        Dockerfile: "Dockerfile",
        Tags:       []string{"alpine-echo:1.2.4"},
    }
    _, err = c.ImageBuild(ctx, reader, buildOptions)
    if err != nil {
        panic(err)
    }
}


docker image lsgo run . 之后的输出:

REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
alpine-echo                      1.2.4               d81774f32812        26 seconds ago      5.55MB
alpine                           3.10.3              b168ac0e770e        4 days ago          5.55MB

docker run alpine-echo:1.2.4 的输出:

this is from the archive

注意:您可能需要针对您的特定版本编辑 FROM alpine:3.10.3