Skip to content

Commit 410d2c8

Browse files
authored
feat: Add Docker build context (#1536)
1 parent 874a006 commit 410d2c8

File tree

11 files changed

+217
-56
lines changed

11 files changed

+217
-56
lines changed

docs/api/create_docker_image.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,19 @@ await futureImage.CreateAsync()
1616
.ConfigureAwait(false);
1717
```
1818

19+
To build a Docker image with Testcontainers, it's important to understand the build context. Testcontainers needs three things:
20+
21+
1. **Docker build context**: The directory containing files Docker can use during the build
22+
2. **Dockerfile name**: The name of the Dockerfile to use
23+
3. **Dockerfile directory**: Where the Dockerfile is located
24+
1925
!!!tip
2026

21-
The Dockerfile must be part of the build context, otherwise the build fails.
27+
The build context is optional. If you don't specify one, it defaults to the Dockerfile directory.
28+
29+
Testcontainers creates a tarball with all files and subdirectorys in the build context, incl. the Dockerfile. This tarball is sent to the Docker daemon to build the image. The build context acts as the root for all file operations in the Dockerfile, so all paths (like `COPY` commands) must be relative to it.
2230

23-
It is essential to take into account and comprehend the build context to enable Testcontainers to build the Docker image. Testcontainers generates a tarball that contains all the files and subdirectories within the build context. The tarball is passed to the Docker daemon to build the image. The tarball serves as the new root of the Dockerfile's content. Therefore, all paths must be relative to the new root. If your app or service follows to the following project structure, the build context is `/Users/testcontainers/WeatherForecast/`.
31+
For example, if your project looks like this, the build context would be: `/Users/testcontainers/WeatherForecast/`.
2432

2533
/
2634
└── Users/
@@ -61,6 +69,17 @@ RUN dotnet publish $SLN_FILE_PATH --configuration Release --framework net6.0 --o
6169
ENTRYPOINT ["dotnet", "/app/WeatherForecast.dll"]
6270
```
6371

72+
### Choosing a build context
73+
74+
You can use `WithContextDirectory(string)` to set a build context separate from your Dockerfile. This is useful when the Dockerfile is in one directory but the files you want to include are in another.
75+
76+
```csharp
77+
_ = new ImageFromDockerfileBuilder()
78+
.WithContextDirectory("/path/to/build/context")
79+
.WithDockerfile("Dockerfile")
80+
.WithDockerfileDirectory("/path/to/dockerfile/directory");
81+
```
82+
6483
## Delete multi-stage intermediate layers
6584

6685
A multi-stage Docker image build generates intermediate layers that serve as caches. Testcontainers' Resource Reaper is unable to automatically delete these layers after the test execution. The necessary label is not forwarded by the Docker image build. Testcontainers is unable to track the intermediate layers during the test. To delete the intermediate layers after the test execution, pass the Resource Reaper session to each stage.
@@ -92,8 +111,9 @@ _ = new ImageFromDockerfileBuilder()
92111
| `WithCleanUp` | Will remove the image automatically after all tests have been run. |
93112
| `WithLabel` | Applies metadata to the image e.g. `-l`, `--label "testcontainers=awesome"`. |
94113
| `WithName` | Sets the image name e.g. `-t`, `--tag "testcontainers:0.1.0"`. |
114+
| `WithContextDirectory` | Sets the Docker build context directory. |
95115
| `WithDockerfile` | Sets the name of the `Dockerfile`. |
96-
| `WithDockerfileDirectory` | Sets the build context (directory path that contains the `Dockerfile`). |
116+
| `WithDockerfileDirectory` | Sets the directory path that contains the `Dockerfile`. |
97117
| `WithImageBuildPolicy` | Specifies an image build policy to determine when an image is built. |
98118
| `WithDeleteIfExists` | Will remove the image if it already exists. |
99119
| `WithBuildArgument` | Sets build-time variables e.g `--build-arg "MAGIC_NUMBER=42"`. |

src/Testcontainers/Builders/IImageFromDockerfileBuilder`1.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,35 @@ public interface IImageFromDockerfileBuilder<out TBuilderEntity>
2929
TBuilderEntity WithName(IImage image);
3030

3131
/// <summary>
32-
/// Sets the Dockerfile.
32+
/// Sets the directory to use as the Docker build context.
33+
/// This is the directory that Docker will use to resolve files referenced in the Dockerfile.
3334
/// </summary>
34-
/// <param name="dockerfile">An absolute path or a name value within the Docker build context.</param>
35+
/// <param name="contextDirectory">An absolute path or relative name of the directory to use as the Docker build context.</param>
36+
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
37+
[PublicAPI]
38+
TBuilderEntity WithContextDirectory(string contextDirectory);
39+
40+
/// <summary>
41+
/// Sets the path to the Dockerfile to use for the build.
42+
/// </summary>
43+
/// <param name="dockerfile">The filename or path of the Dockerfile.</param>
3544
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
3645
[PublicAPI]
3746
TBuilderEntity WithDockerfile(string dockerfile);
3847

3948
/// <summary>
40-
/// Sets the Dockerfile directory.
49+
/// Sets the directory containing the Dockerfile.
4150
/// </summary>
42-
/// <param name="dockerfileDirectory">An absolute path or a name value to the Docker build context.</param>
51+
/// <param name="dockerfileDirectory">An absolute path or relative path to the directory containing the Dockerfile.</param>
4352
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
4453
[PublicAPI]
4554
TBuilderEntity WithDockerfileDirectory(string dockerfileDirectory);
4655

4756
/// <summary>
48-
/// Sets the Dockerfile directory.
57+
/// Sets the directory containing the Dockerfile.
4958
/// </summary>
5059
/// <param name="commonDirectoryPath">A common directory path that contains the Dockerfile directory.</param>
51-
/// <param name="dockerfileDirectory">A relative path or a name value to the Docker build context.</param>
60+
/// <param name="dockerfileDirectory">An absolute path or relative path to the directory containing the Dockerfile.</param>
5261
/// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns>
5362
[PublicAPI]
5463
TBuilderEntity WithDockerfileDirectory(CommonDirectoryPath commonDirectoryPath, string dockerfileDirectory);

src/Testcontainers/Builders/ImageFromDockerfileBuilder.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ public ImageFromDockerfileBuilder WithName(IImage image)
6363
return Merge(DockerResourceConfiguration, new ImageFromDockerfileConfiguration(image: image.ApplyHubImageNamePrefix()));
6464
}
6565

66+
/// <inheritdoc />
67+
public ImageFromDockerfileBuilder WithContextDirectory(string contextDirectory)
68+
{
69+
return Merge(DockerResourceConfiguration, new ImageFromDockerfileConfiguration(contextDirectory: contextDirectory));
70+
}
71+
6672
/// <inheritdoc />
6773
public ImageFromDockerfileBuilder WithDockerfile(string dockerfile)
6874
{

src/Testcontainers/Clients/TestcontainersClient.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ public async Task<string> BuildAsync(IImageFromDockerfileConfiguration configura
372372
if (configuration.ImageBuildPolicy(cachedImage))
373373
{
374374
var dockerfileArchive = new DockerfileArchive(
375+
configuration.ContextDirectory,
375376
configuration.DockerfileDirectory,
376377
configuration.Dockerfile,
377378
configuration.Image,

src/Testcontainers/Configurations/Images/IImageFromDockerfileConfiguration.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ public interface IImageFromDockerfileConfiguration : IResourceConfiguration<Imag
1717
/// </summary>
1818
bool? DeleteIfExists { get; }
1919

20+
/// <summary>
21+
/// Gets the context directory.
22+
/// </summary>
23+
string ContextDirectory { get; }
24+
2025
/// <summary>
2126
/// Gets the Dockerfile.
2227
/// </summary>

src/Testcontainers/Configurations/Images/ImageFromDockerfileConfiguration.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ internal sealed class ImageFromDockerfileConfiguration : ResourceConfiguration<I
1515
/// <summary>
1616
/// Initializes a new instance of the <see cref="ImageFromDockerfileConfiguration" /> class.
1717
/// </summary>
18+
/// <param name="contextDirectory">The context directory.</param>
1819
/// <param name="dockerfile">The Dockerfile.</param>
1920
/// <param name="dockerfileDirectory">The Dockerfile directory.</param>
2021
/// <param name="target">The target.</param>
@@ -23,6 +24,7 @@ internal sealed class ImageFromDockerfileConfiguration : ResourceConfiguration<I
2324
/// <param name="buildArguments">A list of build arguments.</param>
2425
/// <param name="deleteIfExists">A value indicating whether Testcontainers removes an existing image or not.</param>
2526
public ImageFromDockerfileConfiguration(
27+
string contextDirectory = null,
2628
string dockerfile = null,
2729
string dockerfileDirectory = null,
2830
string target = null,
@@ -31,6 +33,7 @@ public ImageFromDockerfileConfiguration(
3133
IReadOnlyDictionary<string, string> buildArguments = null,
3234
bool? deleteIfExists = null)
3335
{
36+
ContextDirectory = contextDirectory;
3437
Dockerfile = dockerfile;
3538
DockerfileDirectory = dockerfileDirectory;
3639
Target = target;
@@ -66,6 +69,7 @@ public ImageFromDockerfileConfiguration(IImageFromDockerfileConfiguration resour
6669
public ImageFromDockerfileConfiguration(IImageFromDockerfileConfiguration oldValue, IImageFromDockerfileConfiguration newValue)
6770
: base(oldValue, newValue)
6871
{
72+
ContextDirectory = BuildConfiguration.Combine(oldValue.ContextDirectory, newValue.ContextDirectory);
6973
Dockerfile = BuildConfiguration.Combine(oldValue.Dockerfile, newValue.Dockerfile);
7074
DockerfileDirectory = BuildConfiguration.Combine(oldValue.DockerfileDirectory, newValue.DockerfileDirectory);
7175
Target = BuildConfiguration.Combine(oldValue.Target, newValue.Target);
@@ -79,6 +83,10 @@ public ImageFromDockerfileConfiguration(IImageFromDockerfileConfiguration oldVal
7983
[JsonIgnore]
8084
public bool? DeleteIfExists { get; }
8185

86+
/// <inheritdoc />
87+
[JsonIgnore]
88+
public string ContextDirectory { get; }
89+
8290
/// <inheritdoc />
8391
[JsonIgnore]
8492
public string Dockerfile { get; }

0 commit comments

Comments
 (0)