Skip to content

Commit 7938cc2

Browse files
committed
updated docker compose redme
1 parent 8a50d01 commit 7938cc2

File tree

5 files changed

+203
-111
lines changed

5 files changed

+203
-111
lines changed

core/docker-compose-test.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
version: '2.2'
2+
services:
3+
http:
4+
build: .
5+
image: python:latest
6+
ports:
7+
- 8080:8080
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package org.testcontainers.containers;
2+
3+
import org.junit.Ignore;
4+
import org.junit.Test;
5+
import org.testcontainers.containers.wait.strategy.Wait;
6+
7+
import java.io.File;
8+
import java.time.Duration;
9+
import java.util.List;
10+
import java.util.stream.Collectors;
11+
import java.util.stream.Stream;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
import static org.assertj.core.api.Assertions.catchThrowable;
15+
16+
public class ComposeContainerDocTest {
17+
18+
private static final int REDIS_PORT = 6379;
19+
private static final int POSTGRES_PORT = 5432;
20+
private static final String DOCKER_COMPOSE_FILE_PATH = "src/test/resources/v2-compose-test-doc.yml";
21+
public static final String ENV_FILE_NAME = "v2-compose-test-doc.env";
22+
23+
@Test
24+
public void testComposeContainerConstructor() {
25+
try (
26+
// composeContainerConstructor {
27+
ComposeContainer compose = new ComposeContainer(new File(DOCKER_COMPOSE_FILE_PATH))
28+
.withExposedService("redis-1", REDIS_PORT)
29+
.withExposedService("postgres-1", POSTGRES_PORT)
30+
// }
31+
) {
32+
compose.start();
33+
34+
// getServiceHostAndPort {
35+
String redisUrl = String.format("%s:%s",
36+
compose.getServiceHost("redis-1", REDIS_PORT),
37+
compose.getServicePort("redis-1", REDIS_PORT)
38+
);
39+
// }
40+
assertThat(redisUrl).isNotBlank();
41+
42+
containsStartedServices(compose, "redis-1", "postgres-1");
43+
}
44+
}
45+
46+
@Test
47+
public void testComposeContainerWithCombinedWaitStrategies() {
48+
try (
49+
// composeContainerWithCombinedWaitStrategies {
50+
ComposeContainer compose = new ComposeContainer(new File(DOCKER_COMPOSE_FILE_PATH))
51+
.withExposedService("redis-1", REDIS_PORT,
52+
Wait.forSuccessfulCommand("redis-cli ping"))
53+
.withExposedService("postgres-1", POSTGRES_PORT,
54+
Wait.forLogMessage(".*database system is ready to accept connections.*\\n", 1))
55+
// }
56+
){
57+
compose.start();
58+
containsStartedServices(compose, "redis-1", "postgres-1");
59+
}
60+
}
61+
62+
@Test
63+
public void testComposeContainerWaitForPortWithTimeout() {
64+
try (
65+
// composeContainerWaitForPortWithTimeout {
66+
ComposeContainer compose = new ComposeContainer(new File(DOCKER_COMPOSE_FILE_PATH))
67+
.withExposedService("redis-1", REDIS_PORT,
68+
Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(30)))
69+
// }
70+
){
71+
compose.start();
72+
containsStartedServices(compose, "redis-1");
73+
}
74+
}
75+
76+
@Test
77+
public void testComposeContainerWithLocalCompose() {
78+
try (
79+
// composeContainerWithLocalCompose {
80+
ComposeContainer compose = new ComposeContainer(new File(DOCKER_COMPOSE_FILE_PATH))
81+
.withExposedService("redis-1", REDIS_PORT)
82+
.withLocalCompose(true)
83+
// }
84+
){
85+
compose.start();
86+
containsStartedServices(compose, "redis-1");
87+
}
88+
}
89+
90+
@Test
91+
public void test() {
92+
try (
93+
// composeContainerWithCopyFiles {
94+
ComposeContainer compose = new ComposeContainer(new File(DOCKER_COMPOSE_FILE_PATH))
95+
.withExposedService("postgres-1", POSTGRES_PORT)
96+
.withCopyFilesInContainer(ENV_FILE_NAME)
97+
// }
98+
){
99+
compose.start();
100+
containsStartedServices(compose, "postgres-1");
101+
}
102+
}
103+
104+
private void containsStartedServices(ComposeContainer compose, String... expectedServices) {
105+
final List<String> containerNames = compose.listChildContainers()
106+
.stream()
107+
.flatMap(it -> Stream.of(it.getNames()))
108+
.collect(Collectors.toList());
109+
110+
for (String service: expectedServices) {
111+
assertThat(containerNames)
112+
.anyMatch(it -> it.endsWith(service));
113+
}
114+
}
115+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
POSTGRES_USER=postgres
2+
POSTGRES_PASSWORD=postgres
3+
POSTGRES_DB=postgres
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: '3.8'
2+
3+
services:
4+
redis:
5+
image: redis:7
6+
postgres:
7+
image: postgres:17-alpine
8+
environment:
9+
POSTGRES_USER: myuser
10+
POSTGRES_PASSWORD: mypassword
11+
POSTGRES_DB: mydatabase

docs/modules/docker_compose.md

Lines changed: 67 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -2,158 +2,114 @@
22

33
## Benefits
44

5-
Similar to generic containers support, it's also possible to run a bespoke set of services
6-
specified in a `docker-compose.yml` file.
5+
Similar to generic container support, it's also possible to run a bespoke set of services specified in a `docker-compose.yml` file.
76

8-
This is intended to be useful on projects where Docker Compose is already used in dev or other environments to define
9-
services that an application may be dependent upon.
7+
This is especially useful for projects where Docker Compose is already used in development or other environments to define services that an application may be dependent upon.
108

11-
Behind the scenes, Testcontainers actually launches a temporary Docker Compose client - in a container, of course, so
12-
it's not necessary to have it installed on all developer/test machines.
9+
The [`ComposeContainer`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/ComposeContainer.html) leverages [Compose V2](https://www.docker.com/blog/announcing-compose-v2-general-availability/), making it easy to use the same dependencies from the development environment within tests.
1310

1411
## Example
1512

16-
A single class rule, pointing to a `docker-compose.yml` file, should be sufficient to launch any number of services
17-
required by your tests:
18-
```java
19-
@ClassRule
20-
public static DockerComposeContainer environment =
21-
new DockerComposeContainer(new File("src/test/resources/compose-test.yml"))
22-
.withExposedService("redis_1", REDIS_PORT)
23-
.withExposedService("elasticsearch_1", ELASTICSEARCH_PORT);
24-
```
13+
A single class `ComposeContainer`, defined based on a `docker-compose.yml` file, should be sufficient to launch any number of services required by our tests:
14+
15+
<!--codeinclude-->
16+
[Create a ComposeContainer](../../core/src/test/java/org/testcontainers/containers/ComposeContainerDocTest.java) inside_block:composeContainerConstructor
17+
<!--/codeinclude-->
18+
19+
!!! note
20+
Make sure the service names use a `-` rather than `_` as separator.
2521

26-
In this example, `compose-test.yml` should have content such as:
22+
In this example, Docker Compose file should have content such as:
2723
```yaml
28-
redis:
29-
image: redis
30-
elasticsearch:
31-
image: elasticsearch
24+
version: '3.8'
25+
26+
services:
27+
redis:
28+
image: redis:7
29+
postgres:
30+
image: postgres:17-alpine
31+
environment:
32+
POSTGRES_USER: postgres
3233
```
3334
34-
Note that it is not necessary to define ports to be exposed in the YAML file; this would inhibit reuse/inclusion of the
35-
file in other contexts.
35+
Note that it is not necessary to define ports to be exposed in the YAML file, as this would inhibit the reuse/inclusion of the file in other contexts.
36+
37+
Instead, Testcontainers will spin up a small 'ambassador' container, which will proxy between the Compose-managed containers and ports that are accessible to our tests. This is done using a separate, minimal container that runs socat as a TCP proxy.
38+
39+
## ComposeContainer vs DockerComposeContainer
3640
37-
Instead, Testcontainers will spin up a small 'ambassador' container, which will proxy
38-
between the Compose-managed containers and ports that are accessible to your tests. This is done using a separate, minimal
39-
container that runs socat as a TCP proxy.
41+
So far, we discussed [`ComposeContainer`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/ComposeContainer.html), which is the equivalent of docker compose [version 2](https://www.docker.com/blog/announcing-compose-v2-general-availability/).
4042

41-
## Accessing a container from tests
43+
On the other hand, [`DockerComposeContainer`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/DockerComposeContainer.html) utilizes Compose V1, which has been marked deprecated by Docker.
4244

43-
The rule provides methods for discovering how your tests can interact with the containers:
45+
The two APIs are quite similar, and most examples provided on this page can be applied to both of them.
46+
47+
## Accessing a Container
48+
49+
ComposeContainer provides methods for discovering how your tests can interact with the containers:
4450

4551
* `getServiceHost(serviceName, servicePort)` returns the IP address where the container is listening (via an ambassador
4652
container)
4753
* `getServicePort(serviceName, servicePort)` returns the Docker mapped port for a port that has been exposed (via an
4854
ambassador container)
4955

50-
For example, with the Redis example above, the following will allow your tests to access the Redis service:
51-
```java
52-
String redisUrl = environment.getServiceHost("redis_1", REDIS_PORT)
53-
+ ":" +
54-
environment.getServicePort("redis_1", REDIS_PORT);
55-
```
56+
Let's use this API to create the URL that will enable our tests to access the Redis service:
57+
<!--codeinclude-->
58+
[Access a Service's host and port](../../core/src/test/java/org/testcontainers/containers/ComposeContainerDocTest.java) inside_block:getServiceHostAndPort
59+
<!--/codeinclude-->
5660

57-
## Startup timeout
61+
## Wait Strategies and Startup Timeouts
5862
Ordinarily Testcontainers will wait for up to 60 seconds for each exposed container's first mapped network port to start listening.
5963

6064
This simple measure provides a basic check whether a container is ready for use.
6165

62-
There are overloaded `withExposedService` methods that take a `WaitStrategy` so you can specify a timeout strategy per container.
66+
There are overloaded `withExposedService` methods that take a [`WaitStrategy`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/WaitStrategy.html) where we can specify a timeout strategy per container. We can either use the fluent API to crate a custom strategy or use one of the already existing ones, accessible via the static factory methods from of the [`Wait`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/Wait.html) class.
6367

64-
### Waiting for startup examples
68+
For instance, we can wait for exposed port and set a custom timeout:
69+
<!--codeinclude-->
70+
[Wait for the exposed port and use a custom timeout](../../core/src/test/java/org/testcontainers/containers/ComposeContainerDocTest.java) inside_block:composeContainerWaitForPortWithTimeout
71+
<!--/codeinclude-->
6572

66-
Waiting for exposed port to start listening:
67-
```java
68-
@ClassRule
69-
public static DockerComposeContainer environment =
70-
new DockerComposeContainer(new File("src/test/resources/compose-test.yml"))
71-
.withExposedService("redis_1", REDIS_PORT,
72-
Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(30)));
73-
```
73+
Needless to say, we can define different strategies for each service in our Docker Compose setup.
7474

75-
Wait for arbitrary status codes on an HTTPS endpoint:
76-
```java
77-
@ClassRule
78-
public static DockerComposeContainer environment =
79-
new DockerComposeContainer(new File("src/test/resources/compose-test.yml"))
80-
.withExposedService("elasticsearch_1", ELASTICSEARCH_PORT,
81-
Wait.forHttp("/all")
82-
.forStatusCode(200)
83-
.forStatusCode(401)
84-
.usingTls());
85-
```
75+
For example, our Redis container can wait for a successful redis-cli command, while Postgres waits for a specific log message:
8676

87-
Separate wait strategies for each container:
88-
```java
89-
@ClassRule
90-
public static DockerComposeContainer environment =
91-
new DockerComposeContainer(new File("src/test/resources/compose-test.yml"))
92-
.withExposedService("redis_1", REDIS_PORT, Wait.forListeningPort())
93-
.withExposedService("elasticsearch_1", ELASTICSEARCH_PORT,
94-
Wait.forHttp("/all")
95-
.forStatusCode(200)
96-
.forStatusCode(401)
97-
.usingTls());
98-
```
99-
100-
Alternatively, you can use `waitingFor(serviceName, waitStrategy)`,
101-
for example if you need to wait on a log message from a service, but don't need to expose a port.
77+
<!--codeinclude-->
78+
[Wait for a custom command and a log message](../../core/src/test/java/org/testcontainers/containers/ComposeContainerDocTest.java) inside_block:composeContainerWithCombinedWaitStrategies
79+
<!--/codeinclude-->
10280

103-
```java
104-
@ClassRule
105-
public static DockerComposeContainer environment =
106-
new DockerComposeContainer(new File("src/test/resources/compose-test.yml"))
107-
.withExposedService("redis_1", REDIS_PORT, Wait.forListeningPort())
108-
.waitingFor("db_1", Wait.forLogMessage("started", 1));
109-
```
81+
!!! More on Wait Strategies
11082

111-
## 'Local compose' mode
112-
113-
You can override Testcontainers' default behaviour and make it use a `docker-compose` binary installed on the local machine.
114-
This will generally yield an experience that is closer to running docker-compose locally, with the caveat that Docker Compose needs to be present on dev and CI machines.
115-
```java
116-
public static DockerComposeContainer environment =
117-
new DockerComposeContainer(new File("src/test/resources/compose-test.yml"))
118-
.withExposedService("redis_1", REDIS_PORT, Wait.forListeningPort())
119-
.waitingFor("db_1", Wait.forLogMessage("started", 1))
120-
.withLocalCompose(true);
121-
```
83+
Refer to the documentation to learn how to use the API to create tailor-made strategies for [waiting until containers are fully started and ready](../features/startup_and_waits.md).
12284

123-
## Compose V2
85+
## The 'Local Compose' Mode
12486

125-
[Compose V2 is GA](https://www.docker.com/blog/announcing-compose-v2-general-availability/) and it relies on the `docker` command itself instead of `docker-compose`.
126-
Testcontainers provides `ComposeContainer` if you want to use Compose V2.
87+
We can override Testcontainers' default behaviour and make it use a `docker-compose` binary installed on the local machine.
12788

128-
```java
129-
public static ComposeContainer environment =
130-
new ComposeContainer(new File("src/test/resources/compose-test.yml"))
131-
.withExposedService("redis-1", REDIS_PORT, Wait.forListeningPort())
132-
.waitingFor("db-1", Wait.forLogMessage("started", 1));
133-
```
89+
This will generally yield an experience that is closer to running _docker compose_ locally, with the caveat that Docker Compose needs to be present on dev and CI machines.
13490

135-
!!! note
136-
Make sure the service name use a `-` instead of `_` as separator using `ComposeContainer`.
91+
<!--codeinclude-->
92+
[Use ComposeContainer in 'Local Compose' mode](../../core/src/test/java/org/testcontainers/containers/ComposeContainerDocTest.java) inside_block:composeContainerWithLocalCompose
93+
<!--/codeinclude-->
13794

138-
## Build working directory
95+
## Build Working Directory
13996

140-
You can select what files should be copied only via `withCopyFilesInContainer`:
97+
We can select what files should be copied only via `withCopyFilesInContainer`:
14198

142-
```java
143-
public static ComposeContainer environment =
144-
new ComposeContainer(new File("compose.yml"))
145-
.withCopyFilesInContainer(".env");
146-
```
99+
<!--codeinclude-->
100+
[Use ComposeContainer in 'Local Compose' mode](../../core/src/test/java/org/testcontainers/containers/ComposeContainerDocTest.java) inside_block:composeContainerWithCopyFiles
101+
<!--/codeinclude-->
147102

148-
In this example, only `compose.yml` and `.env` are copied over into the container that will run the Docker Compose file.
103+
In this example, only docker compose and env files are copied over into the container that will run the Docker Compose file.
149104
By default, all files in the same directory as the compose file are copied over.
150105

151-
This can be used with `DockerComposeContainer` and `ComposeContainer`.
152-
You can use file and directory references.
153-
They are always resolved relative to the directory where the compose file resides.
106+
We can use file and directory references. They are always resolved relative to the directory where the compose file resides.
107+
108+
This can be used with [`DockerComposeContainer`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/DockerComposeContainer.html) and [`ComposeContainer`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/ComposeContainer.html).
154109

155110
!!! note
156-
This only work with containarized Compose, not with `Local Compose` mode.
111+
112+
This can be used with [`DockerComposeContainer`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/DockerComposeContainer.html) and [`ComposeContainer`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/ComposeContainer.html) - but **only in the containerized Compose (not with `Local Compose` mode)**.
157113

158114
## Using private repositories in Docker compose
159115
When Docker Compose is used in container mode (not local), it's needs to be made aware of Docker settings for private repositories.

0 commit comments

Comments
 (0)