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: "test-volume",
},
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:
- 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)
}
container, 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
: aio.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.
- Using the
CopyFileToContainer
method on arunning
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)
}
container, 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,
})
if err != nil {
t.Fatal(err)
}
err = container.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.:
- The first way is using the
Files
field in theContainerRequest
struct, as shown in the previous section, but using the path of a directory asHostFilePath
. Like so:
dataDirectory, err := filepath.Abs(filepath.Join(".", "testdata"))
if err != nil {
t.Fatal(err)
}
container, 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,
})
- The second way uses the existing
CopyFileToContainer
method, which will internally check if the host path is a directory, calling theCopyDirToContainer
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)
}
container, 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,
})
if err != nil {
t.Fatal(err)
}
// as the container is started, we can create the directory first
_, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"})
if err != nil {
t.Fatal(err)
}
// because the container path is a directory, it will use the copy dir method as fallback
err = container.CopyFileToContainer(ctx, dataDirectory, "/scripts", 0o700)
if err != nil {
t.Fatal(err)
}
- 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)
}
container, 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,
})
if err != nil {
t.Fatal(err)
}
// as the container is started, we can create the directory first
_, _, err = container.Exec(ctx, []string{"mkdir", "-p", "/scripts"})
if err != nil {
t.Fatal(err)
}
err = container.CopyDirToContainer(ctx, dataDirectory, "/scripts", 0o700)
if err != nil {
t.Fatal(err)
}