Skip to content

Copying data into a container

Copying data of any type into a container is a very common practice when working with containers. This section will show you how to do it using Testcontainers for Go.

Volume mapping

It is possible to map a Docker volume into the container using the Mounts attribute at the ContainerRequest struct. For that, please pass an instance of the GenericVolumeMountSource type, which allows you to specify the name of the volume to be mapped, and the path inside the container where it should be mounted:

req := testcontainers.ContainerRequest{
    Image: "alpine",
    Mounts: testcontainers.ContainerMounts{
        {
            Source: testcontainers.GenericVolumeMountSource{
                Name: volumeName,
            },
            Target: "/data",
        },
    },
}

Tip

This ability of creating volumes is also available for remote Docker hosts.

Warning

Bind mounts are not supported, as it could not work with remote Docker hosts.

Tip

It is recommended to copy data from your local host machine to a test container using the file copy API described below, as it is much more portable.

Copying files to a container

If you would like to copy a file to a container, you can do it in two different manners:

  1. Adding a list of files in the ContainerRequest, which will be copied before the container starts:
absPath, err := filepath.Abs(filepath.Join(".", "testdata", "hello.sh"))
if err != nil {
    t.Fatal(err)
}

r, err := os.Open(absPath)
if err != nil {
    t.Fatal(err)
}

ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
    ContainerRequest: testcontainers.ContainerRequest{
        Image: "docker.io/bash",
        Files: []testcontainers.ContainerFile{
            {
                Reader:            r,
                HostFilePath:      absPath, // will be discarded internally
                ContainerFilePath: "/hello.sh",
                FileMode:          0o700,
            },
        },
        Cmd:        []string{"bash", "/hello.sh"},
        WaitingFor: wait.ForLog("done"),
    },
    Started: true,
})

The ContainerFile struct will accept the following fields:

  • HostFilePath: the path to the file in the host machine. Optional (see below).
  • Reader: a io.Reader that will be used to copy the file to the container. Optional.
  • ContainerFilePath: the path to the file in the container. Mandatory.
  • Mode: the file mode, which is optional.

Info

If the Reader field is set, the HostFilePath field will be ignored.

  1. Using the CopyFileToContainer method on a running container:
waitForPath, err := filepath.Abs(filepath.Join(".", "testdata", "waitForHello.sh"))
if err != nil {
    t.Fatal(err)
}
helloPath, err := filepath.Abs(filepath.Join(".", "testdata", "hello.sh"))
if err != nil {
    t.Fatal(err)
}

ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
    ContainerRequest: testcontainers.ContainerRequest{
        Image: "docker.io/bash:5.2.26",
        Files: []testcontainers.ContainerFile{
            {
                HostFilePath:      waitForPath,
                ContainerFilePath: "/waitForHello.sh",
                FileMode:          0o700,
            },
        },
        Cmd: []string{"bash", "/waitForHello.sh"},
    },
    Started: true,
})
testcontainers.CleanupContainer(t, ctr)
require.NoError(t, err)

err = ctr.CopyFileToContainer(ctx, helloPath, "/scripts/hello.sh", 0o700)
#!/bin/bash

file=/scripts/hello.sh

until [ -s "$file" ]
do
    sleep 0.1
done

sh $file

Copying directories to a container

It's also possible to copy an entire directory to a container, and that can happen before and/or after the container gets into the Running state. As an example, you could need to bulk-copy a set of files, such as a configuration directory that does not exist in the underlying Docker image.

It's important to notice that, when copying the directory to the container, the container path must exist in the Docker image. And this is a strong requirement for files to be copied before the container is started, as we cannot create the full path at that time.

You can leverage the very same mechanism used for copying files to a container, but for directories.:

  1. The first way is using the Files field in the ContainerRequest struct, as shown in the previous section, but using the path of a directory as HostFilePath. Like so:
dataDirectory, err := filepath.Abs(filepath.Join(".", "testdata"))
if err != nil {
    t.Fatal(err)
}

ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
    ContainerRequest: testcontainers.ContainerRequest{
        Image: "docker.io/bash",
        Files: []testcontainers.ContainerFile{
            {
                HostFilePath: dataDirectory,
                // ContainerFile cannot create the parent directory, so we copy the scripts
                // to the root of the container instead. Make sure to create the container directory
                // before you copy a host directory on create.
                ContainerFilePath: "/",
                FileMode:          0o700,
            },
        },
        Cmd:        []string{"bash", "/testdata/hello.sh"},
        WaitingFor: wait.ForLog("done"),
    },
    Started: true,
})
  1. The second way uses the existing CopyFileToContainer method, which will internally check if the host path is a directory, calling the CopyDirToContainer method if needed:
dataDirectory, err := filepath.Abs(filepath.Join(".", "testdata"))
if err != nil {
    t.Fatal(err)
}
waitForPath, err := filepath.Abs(filepath.Join(dataDirectory, "waitForHello.sh"))
if err != nil {
    t.Fatal(err)
}

ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
    ContainerRequest: testcontainers.ContainerRequest{
        Image: "docker.io/bash",
        Files: []testcontainers.ContainerFile{
            {
                HostFilePath:      waitForPath,
                ContainerFilePath: "/waitForHello.sh",
                FileMode:          0o700,
            },
        },
        Cmd: []string{"bash", "/waitForHello.sh"},
    },
    Started: true,
})
testcontainers.CleanupContainer(t, ctr)
require.NoError(t, err)

// as the container is started, we can create the directory first
_, _, err = ctr.Exec(ctx, []string{"mkdir", "-p", "/scripts"})
require.NoError(t, err)

// because the container path is a directory, it will use the copy dir method as fallback
err = ctr.CopyFileToContainer(ctx, dataDirectory, "/scripts", 0o700)
require.NoError(t, err)
  1. The last third way uses the CopyDirToContainer method, directly, which, as you probably know, needs the existence of the parent directory in order to copy the directory:
waitForPath, err := filepath.Abs(filepath.Join(".", "testdata", "waitForHello.sh"))
if err != nil {
    t.Fatal(err)
}
dataDirectory, err := filepath.Abs(filepath.Join(".", "testdata"))
if err != nil {
    t.Fatal(err)
}

ctr, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
    ContainerRequest: testcontainers.ContainerRequest{
        Image: "docker.io/bash",
        Files: []testcontainers.ContainerFile{
            {
                HostFilePath:      waitForPath,
                ContainerFilePath: "/waitForHello.sh",
                FileMode:          0o700,
            },
        },
        Cmd: []string{"bash", "/waitForHello.sh"},
    },
    Started: true,
})
testcontainers.CleanupContainer(t, ctr)
require.NoError(t, err)

// as the container is started, we can create the directory first
_, _, err = ctr.Exec(ctx, []string{"mkdir", "-p", "/scripts"})
require.NoError(t, err)

err = ctr.CopyDirToContainer(ctx, dataDirectory, "/scripts", 0o700)
require.NoError(t, err)