Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
/vendor/
/.php-cs-fixer.cache
/.phpunit.cache
/composer.lock
/composer.lock

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"integration": "paratest tests/ --bootstrap vendor/autoload.php -f",
"cs": "php-cs-fixer fix --dry-run",
"cs:fix": "php-cs-fixer fix",
"phpstan": "phpstan analyse --memory-limit=256M"
"phpstan": "phpstan analyse --memory-limit=256M",
"docs:serve": "docker compose up"
},
"config": {
"allow-plugins": {
Expand Down
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
docs:
image: python:3.14
command: sh -c "pip install -r requirements.txt && mkdocs serve -a 0.0.0.0:8000"
working_dir: /docs
volumes:
- ./:/docs
ports:
- 8000:8000
38 changes: 38 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Configuration

## Container runtime access

Testcontainers for PHP uses Docker APIs under the hood, so your test process must be able to reach a Docker-compatible daemon.

- Local socket: `unix:///var/run/docker.sock`
- Remote daemon: configure `DOCKER_HOST`

## Environment variables

### `DOCKER_HOST`

Defines where the Docker API is available.
Examples:

```bash
export DOCKER_HOST=tcp://127.0.0.1:2375
export DOCKER_HOST=unix:///var/run/docker.sock
```

### `TESTCONTAINERS_HOST_OVERRIDE`

Overrides the host address returned by Testcontainers when your tests need a custom endpoint:

```bash
export TESTCONTAINERS_HOST_OVERRIDE=127.0.0.1
```

## Running inside containers

If your tests run inside another container:

- Mount the Docker socket.
- Ensure network routing from test container to started containers is valid.
- Set host overrides when needed for your CI/network topology.

For startup and connectivity failures, see [troubleshooting](troubleshooting.md).
86 changes: 86 additions & 0 deletions docs/features/containers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Containers

## Starting a container

Start any image with `GenericContainer`:

```php
<?php

use Testcontainers\Container\GenericContainer;

$container = (new GenericContainer('alpine:3.20'))
->withCommand(['sleep', 'infinity'])
->start();
```

## Common container options

### Environment variables

```php
$container = (new GenericContainer('alpine:3.20'))
->withEnvironment([
'APP_ENV' => 'test',
'FEATURE_X' => 'enabled',
])
->start();
```

### Exposed ports

```php
$container = (new GenericContainer('nginx:alpine'))
->withExposedPorts(80)
->start();

$host = $container->getHost();
$port = $container->getMappedPort(80);
```

### Files and directories

```php
$container = (new GenericContainer('alpine:3.20'))
->withCommand(['sleep', 'infinity'])
->withCopyFilesToContainer([
['source' => __DIR__ . '/app.conf', 'target' => '/etc/app.conf'],
])
->withCopyContentToContainer([
['content' => 'hello from php', 'target' => '/tmp/message.txt'],
])
->start();
```

### Networking

`withNetwork()` connects the container to an existing Docker network. Create the network before starting the container, for example with `docker network create my-test-network`.

```php
$container = (new GenericContainer('alpine:3.20'))
->withNetwork('my-test-network')
->withAliases(['service-a'])
->start();
```

### User, working directory, labels, and mounts

```php
$container = (new GenericContainer('alpine:3.20'))
->withUser('1000:1000')
->withWorkingDir('/app')
->withLabels(['suite' => 'integration'])
->withMount(__DIR__, '/workspace')
->start();
```

## Stopping and restarting

```php
$container->restart();
$container->stop();
```

`stop()` stops and removes the container.

Related docs: [wait strategies](wait-strategies.md), [networking](networking.md).
66 changes: 66 additions & 0 deletions docs/features/networking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Networking

Testcontainers for PHP maps container ports to random host ports by default.

## Access a service from your test process

Use `getHost()` and `getMappedPort()`:

```php
<?php

declare(strict_types=1);

use Testcontainers\Container\GenericContainer;
use Testcontainers\Wait\WaitForHttp;

$container = (new GenericContainer('nginx:alpine'))
->withExposedPorts(80)
->withWait((new WaitForHttp(80))->withPath('/'))
->start();

$url = sprintf('http://%s:%d', $container->getHost(), $container->getMappedPort(80));
echo $url . PHP_EOL;

$container->stop();
```

## Use the first mapped port

If a container exposes only one port, use `getFirstMappedPort()`:

```php
$port = $container->getFirstMappedPort();
```

## Join a Docker network

Connect multiple containers to the same existing Docker network and use aliases. Testcontainers for PHP does not create Docker networks, so create the network before starting containers:

```bash
docker network create my-test-network
```

```php
<?php

declare(strict_types=1);

use Testcontainers\Container\GenericContainer;

$container = (new GenericContainer('alpine'))
->withCommand(['tail', '-f', '/dev/null'])
->withNetwork('my-test-network')
->withAliases(['service-a'])
->start();

$container->stop();
```

## Notes

- Do not hardcode localhost ports in tests.
- Always resolve endpoints from `getHost()` and mapped ports.
- Named networks and aliases are useful for container-to-container communication.

Related docs: [containers](containers.md), [configuration](../configuration.md), [troubleshooting](../troubleshooting.md).
101 changes: 101 additions & 0 deletions docs/features/wait-strategies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Wait strategies

Wait strategies define when a container is ready to use.

`GenericContainer` defaults to a running-state check (`WaitForContainer`).
For application readiness, prefer explicit strategies described below.

## Host port (default-friendly)

```php
<?php

use Testcontainers\Container\GenericContainer;
use Testcontainers\Wait\WaitForHostPort;

$container = (new GenericContainer('redis:7'))
->withExposedPorts(6379)
->withWait(new WaitForHostPort())
->start();
```

## Log output

```php
use Testcontainers\Wait\WaitForLog;

$container = (new GenericContainer('redis:7'))
->withExposedPorts(6379)
->withWait(new WaitForLog('Ready to accept connections'))
->start();
```

With regular expression matching:

```php
$container = (new GenericContainer('opensearchproject/opensearch:latest'))
->withExposedPorts(9200)
->withWait(new WaitForLog('/\]\s+started\?\[/', true, 30_000))
->start();
```

## HTTP checks

```php
use Testcontainers\Wait\WaitForHttp;

$container = (new GenericContainer('nginx:alpine'))
->withExposedPorts(80)
->withWait(
(new WaitForHttp(80))
->withPath('/')
->withExpectedStatusCode(200)
)
->start();
```

## Exec command

```php
use Testcontainers\Wait\WaitForExec;

$container = (new GenericContainer('mysql:8.0'))
->withExposedPorts(3306)
->withEnvironment(['MYSQL_ROOT_PASSWORD' => 'root'])
->withWait(new WaitForExec(['mysqladmin', 'ping', '-h', '127.0.0.1']))
->start();
```

With custom validation:

```php
$container = (new GenericContainer('mysql:8.0'))
->withExposedPorts(3306)
->withEnvironment(['MYSQL_ROOT_PASSWORD' => 'root'])
->withWait(
new WaitForExec(
['mysqladmin', 'ping', '-h', '127.0.0.1'],
static function ($exitCode, $output): bool {
return $exitCode === 0 && str_contains($output, 'mysqld is alive');
}
)
)
->start();
```

## Docker health check

```php
use Testcontainers\Wait\WaitForHealthCheck;

$container = (new GenericContainer('alpine'))
->withCommand(['tail', '-f', '/dev/null'])
->withHealthCheckCommand('echo "healthy" || exit 1')
->withWait(new WaitForHealthCheck())
->start();
```

!!! tip
You can tune timeout and polling intervals in wait strategy constructors.

Related docs: [containers](containers.md), [networking](networking.md), [troubleshooting](../troubleshooting.md).
44 changes: 44 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Testcontainers for PHP

<p style="text-align: center"><strong>Not using PHP? Here are other supported languages.</strong></p>
<div class="card-grid">
<a href="https://java.testcontainers.org/" class="card-grid-item"><img src="/site/language-logos/java.svg" alt="Java"/>Java</a>
<a href="https://golang.testcontainers.org/" class="card-grid-item"><img src="/site/language-logos/go.svg" alt="Go"/>Go</a>
<a href="https://dotnet.testcontainers.org/" class="card-grid-item"><img src="/site/language-logos/dotnet.svg" alt=".NET"/>.NET</a>
<a href="https://node.testcontainers.org/" class="card-grid-item"><img src="/site/language-logos/nodejs.svg" alt="Node.js"/>Node.js</a>
<a href="https://cljdoc.org/d/clj-test-containers/clj-test-containers/0.7.4/doc/readme/" class="card-grid-item"><img src="/site/language-logos/clojure.svg" alt="Clojure"/>Clojure</a>
<a href="https://github.com/testcontainers/testcontainers-elixir/" class="card-grid-item"><img src="/site/language-logos/elixir.svg" alt="Elixir"/>Elixir</a>
<a href="https://github.com/testcontainers/testcontainers-hs/" class="card-grid-item"><img src="/site/language-logos/haskell.svg" alt="Haskell"/>Haskell</a>
<a href="https://testcontainers-python.readthedocs.io/en/latest/" class="card-grid-item"><img src="/site/language-logos/python.svg" alt="Python"/>Python</a>
<a href="https://github.com/testcontainers/testcontainers-ruby/" class="card-grid-item"><img src="/site/language-logos/ruby.svg" alt="Ruby"/>Ruby</a>
<a href="https://docs.rs/testcontainers/latest/testcontainers/" class="card-grid-item"><img src="/site/language-logos/rust.svg" alt="Rust"/>Rust</a>
<a href="https://php.testcontainers.org/" class="card-grid-item"><img src="/site/language-logos/php.svg" alt="PHP"/>PHP</a>
<a href="https://github.com/testcontainers/testcontainers-scala/" class="card-grid-item"><img style="width:30px; height:30px" src="/site/language-logos/scala.svg" alt="Scala"/>Scala</a>
</div>

## About

Testcontainers for PHP is a composer package that makes it simple to create and clean up container-based dependencies for automated integration/smoke tests. The clean, easy-to-use API enables developers to programmatically define containers that should be run as part of a test and clean up those resources when the test is done.

## Documentation

- [Install](quickstart/install.md)
- [Usage](quickstart/usage.md)
- [Containers](features/containers.md)
- [Wait strategies](features/wait-strategies.md)
- [Networking](features/networking.md)
- [Modules](modules/redis.md)
- [Configuration](configuration.md)
- [Troubleshooting](troubleshooting.md)

## License

This project is open source and you can have a look at the code on [GitHub](https://github.com/testcontainers/testcontainers-php). See [LICENSE](https://raw.githubusercontent.com/testcontainers/testcontainers-php/main/LICENSE).

## Copyright

Copyright (c) 2022-present Soner Sayakci, Sergei Shitikov and other authors. Check out our [lovely contributors](https://github.com/testcontainers/testcontainers-php/graphs/contributors).

---

Join our [Slack workspace](https://slack.testcontainers.org/) | [Testcontainers OSS](https://java.testcontainers.org/) | [Testcontainers Cloud](https://www.testcontainers.cloud/)
Loading
Loading