or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

container-configuration.mdcontainer-customization.mdcontainer-lifecycle.mddatabase-initialization.mdindex.mdjdbc-connection.mdr2dbc-support.mdurl-parameters-timeouts.md
tile.json

container-customization.mddocs/

Container Customization

This document covers advanced MySQL container customization including environment variables, command overrides, port mapping, logging, and container information retrieval.

Quick Reference

Environment Variables (Fluent Interface, Return SELF):

  • withEnv(String key, String value) - Set single environment variable
  • withEnv(Map<String, String> env) - Set multiple environment variables

Command Override (Fluent Interface, Return SELF):

  • withCommand(String... commandParts) - Override container command

Port Management:

  • withExposedPorts(Integer... ports): SELF - Expose additional ports
  • getMappedPort(int originalPort): Integer - Get mapped host port (requires container to be started)
  • getExposedPorts(): List<Integer> - Get list of exposed ports
  • getLivenessCheckPortNumbers(): Set<Integer> - Get ports used for liveness checks

Logging (Fluent Interface, Return SELF):

  • withLogConsumer(Consumer<OutputFrame> consumer) - Set log consumer

Container Information:

  • getHost(): String - Get container host (typically "localhost", requires container to be started)
  • getMappedPort(int): Integer - Get mapped port for container port (requires container to be started)

Constraints:

  • All customization methods must be called before start()
  • Port and host information methods require container to be started
  • Configuration cannot be changed after container is started

Capabilities

Environment Variables

Set custom environment variables in the MySQL container.

/**
 * Sets a single environment variable in the container.
 * Environment variables are set before the container starts and affect
 * MySQL server initialization and runtime behavior.
 *
 * @param key the environment variable name (must not be null)
 * @param value the environment variable value (can be null or empty)
 * @return self for method chaining (SELF extends MySQLContainer<SELF>)
 * @throws IllegalArgumentException if key is null
 */
SELF withEnv(String key, String value);

/**
 * Sets multiple environment variables in the container.
 * Replaces any previously set environment variables with the same keys.
 *
 * @param env map of environment variable names to values (must not be null)
 * @return self for method chaining (SELF extends MySQLContainer<SELF>)
 * @throws IllegalArgumentException if env is null
 */
SELF withEnv(Map<String, String> env);

Usage Example:

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
import java.util.HashMap;
import java.util.Map;

// Set single environment variable
MySQLContainer<?> mysql1 = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
    .withEnv("MYSQL_ROOT_HOST", "%")  // Allow root login from any host
    .withEnv("TZ", "America/New_York"); // Set timezone

// Set multiple environment variables
Map<String, String> envVars = new HashMap<>();
envVars.put("MYSQL_ROOT_HOST", "%");
envVars.put("TZ", "UTC");
envVars.put("MYSQL_INITDB_SKIP_TZINFO", "1");

MySQLContainer<?> mysql2 = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
    .withEnv(envVars);

mysql1.start();

Common MySQL Environment Variables:

  • MYSQL_ROOT_HOST - Host pattern for root user access (e.g., "%" for all hosts)
  • MYSQL_ROOT_PASSWORD - Root password (automatically set by container)
  • MYSQL_DATABASE - Database name (automatically set by container)
  • MYSQL_USER - User name (automatically set by container)
  • MYSQL_PASSWORD - User password (automatically set by container)
  • MYSQL_ALLOW_EMPTY_PASSWORD - Allow empty root password (automatically set if applicable)
  • TZ - Container timezone (e.g., "UTC", "America/New_York")
  • MYSQL_INITDB_SKIP_TZINFO - Skip loading timezone information

Command Override

Override the default MySQL container command.

/**
 * Overrides the container's command.
 * This replaces the default MySQL server command with a custom one,
 * allowing you to pass custom MySQL server options.
 *
 * The command parts are passed directly to the container's entrypoint.
 * The first part should typically be "mysqld" to start the MySQL server.
 *
 * @param commandParts the command parts to execute (must not be null or empty)
 * @return self for method chaining (SELF extends MySQLContainer<SELF>)
 * @throws IllegalArgumentException if commandParts is null or empty
 */
SELF withCommand(String... commandParts);

Usage Example:

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

// Override command to set MySQL server variables
MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
    .withCommand("mysqld", "--max_connections=500", "--auto_increment_increment=5");

mysql.start();

// Verify the setting
try (Connection conn = mysql.createConnection("")) {
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("SHOW VARIABLES LIKE 'max_connections'");
    rs.next();
    String maxConn = rs.getString("Value");
    // maxConn == "500"
}

Common MySQL Server Options:

  • --max_connections=N - Maximum concurrent connections
  • --auto_increment_increment=N - Auto-increment increment value
  • --auto_increment_offset=N - Auto-increment offset value
  • --max_allowed_packet=N - Maximum packet size
  • --innodb_buffer_pool_size=N - InnoDB buffer pool size
  • --character-set-server=charset - Server character set
  • --general_log=1 - Enable general query log
  • --slow_query_log=1 - Enable slow query log

Exposed Ports

Expose additional container ports beyond the default MySQL port.

/**
 * Exposes additional ports from the container.
 * MySQL port 3306 is exposed by default and does not need to be specified.
 * Additional ports are mapped to random available host ports.
 *
 * @param ports additional port numbers to expose (must not be null)
 * @return self for method chaining (SELF extends MySQLContainer<SELF>)
 * @throws IllegalArgumentException if ports is null
 */
SELF withExposedPorts(Integer... ports);

/**
 * Returns the list of exposed ports.
 * Includes the default MySQL port (3306) and any additional ports.
 * Can be called before or after container is started.
 *
 * @return list of exposed port numbers
 */
List<Integer> getExposedPorts();

Usage Example:

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
import java.util.List;

MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
    .withExposedPorts(3306, 33060); // Add X Protocol port

mysql.start();

List<Integer> exposedPorts = mysql.getExposedPorts();
// exposedPorts contains [3306, 33060]

Integer xProtocolMappedPort = mysql.getMappedPort(33060);

Port Notes:

  • Port 3306 (MySQL) is automatically exposed
  • Additional ports must be explicitly exposed
  • Ports are mapped to random available host ports
  • Use getMappedPort() to retrieve the host port for a container port
  • Mapped ports are only available after container is started

Log Consumer

Capture and process container log output.

/**
 * Sets a consumer to receive container log output.
 * Useful for debugging container issues or monitoring MySQL server logs.
 * The consumer receives log frames as they are produced by the container.
 *
 * @param consumer consumer function that receives log frames (must not be null)
 * @return self for method chaining (SELF extends MySQLContainer<SELF>)
 * @throws IllegalArgumentException if consumer is null
 */
SELF withLogConsumer(Consumer<OutputFrame> consumer);

Usage Example:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.utility.DockerImageName;

Logger logger = LoggerFactory.getLogger(MyTest.class);

MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
    .withLogConsumer(new Slf4jLogConsumer(logger));

mysql.start();
// All MySQL server logs are output via SLF4J logger

Custom Log Consumer:

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.containers.output.OutputFrame;
import org.testcontainers.utility.DockerImageName;

MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
    .withLogConsumer((OutputFrame frame) -> {
        String log = frame.getUtf8String();
        if (log.contains("ERROR")) {
            System.err.println("MySQL Error: " + log);
        }
    });

mysql.start();

Host and Port Information

Retrieve container host and port mapping information.

/**
 * Returns the host that the container is accessible from.
 * Typically "localhost" but may differ in complex networking setups.
 * This method requires the container to be started.
 *
 * @return the container host (typically "localhost")
 * @throws IllegalStateException if called before container is started
 */
String getHost();

/**
 * Returns the host port that is mapped to the specified container port.
 * MySQL port 3306 is automatically mapped to a random available host port.
 * This method requires the container to be started.
 *
 * @param originalPort the container port number
 * @return the mapped host port number
 * @throws IllegalStateException if called before container is started
 * @throws IllegalArgumentException if originalPort is not exposed
 */
Integer getMappedPort(int originalPort);

Usage Example:

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;

MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"));
mysql.start();

String host = mysql.getHost(); // "localhost"
Integer mappedPort = mysql.getMappedPort(MySQLContainer.MYSQL_PORT); // e.g., 32768

String jdbcUrl = String.format("jdbc:mysql://%s:%d/test", host, mappedPort);
// Same as mysql.getJdbcUrl()

Host and Port Notes:

  • Host is typically "localhost" for standard Docker setups
  • Ports are dynamically assigned to avoid conflicts
  • Mapped ports are only available after container starts
  • Use getJdbcUrl() for complete JDBC URL instead of manual construction
  • Methods throw IllegalStateException if called before start()

Liveness Check Ports

Get the ports used for container liveness checks.

/**
 * Returns the set of port numbers used for liveness checks.
 * These are the mapped host ports that Testcontainers checks to verify
 * the container is running and accessible.
 * This method requires the container to be started.
 *
 * @return set of liveness check port numbers
 * @throws IllegalStateException if called before container is started
 */
Set<Integer> getLivenessCheckPortNumbers();

Usage Example:

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
import java.util.Set;

MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"));
mysql.start();

Set<Integer> livenessCheckPorts = mysql.getLivenessCheckPortNumbers();
// Contains the mapped port for MySQL (3306)

Integer mysqlMappedPort = mysql.getMappedPort(MySQLContainer.MYSQL_PORT);
// livenessCheckPorts.contains(mysqlMappedPort) == true

Complete Customization Example

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.utility.DockerImageName;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;

public class CompleteCustomizationExample {

    private static final Logger logger = LoggerFactory.getLogger(CompleteCustomizationExample.class);

    public void runCustomizedContainer() throws Exception {
        // Prepare environment variables
        Map<String, String> env = new HashMap<>();
        env.put("MYSQL_ROOT_HOST", "%");
        env.put("TZ", "UTC");

        // Create highly customized container
        try (MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
                .withDatabaseName("customdb")
                .withUsername("customuser")
                .withPassword("custompass")
                .withEnv(env)
                .withCommand("mysqld", "--max_connections=200", "--character-set-server=utf8mb4")
                .withExposedPorts(3306, 33060)
                .withLogConsumer(new Slf4jLogConsumer(logger))
                .withConfigurationOverride("mysql-custom-config")
                .withInitScript("schema.sql")
                .withStartupTimeoutSeconds(180)
                .withReuse(false)) {

            mysql.start();

            // Get container information
            String host = mysql.getHost();
            Integer mysqlPort = mysql.getMappedPort(3306);
            Integer xProtocolPort = mysql.getMappedPort(33060);

            System.out.println("MySQL accessible at: " + host + ":" + mysqlPort);
            System.out.println("X Protocol accessible at: " + host + ":" + xProtocolPort);

            // Use container
            try (Connection conn = mysql.createConnection("")) {
                // Execute queries...
            }
        }
    }
}

Advanced Customization Patterns

Conditional Customization

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;

boolean isDebugMode = System.getProperty("debug") != null;
boolean isCiEnvironment = System.getenv("CI") != null;

MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
    .withDatabaseName("testdb");

if (isDebugMode) {
    // Enable verbose logging in debug mode
    mysql.withLogConsumer(new Slf4jLogConsumer(logger))
         .withCommand("mysqld", "--general_log=1", "--general_log_file=/var/log/mysql-queries.log");
}

if (isCiEnvironment) {
    // Use longer timeouts in CI
    mysql.withStartupTimeoutSeconds(300)
         .withConnectTimeoutSeconds(120);
}

mysql.start();

Custom Health Check

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;
import java.time.Duration;

MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
    .withDatabaseName("testdb")
    .waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofMinutes(3)));

mysql.start();

Network Configuration

For connecting multiple containers:

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.utility.DockerImageName;

Network network = Network.newNetwork();

MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
    .withNetwork(network)
    .withNetworkAliases("mysql-server")
    .withDatabaseName("shareddb");

// Another container can connect to this MySQL at "mysql-server:3306"
mysql.start();

Dockerfile-based Customization

For complex customization, use a custom Dockerfile:

# Dockerfile.mysql-custom
FROM mysql:8.0

# Install additional tools
RUN apt-get update && apt-get install -y \
    mysql-client \
    vim

# Copy custom configuration
COPY my-custom.cnf /etc/mysql/conf.d/

# Copy custom scripts
COPY custom-init.sh /docker-entrypoint-initdb.d/
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.images.builder.ImageFromDockerfile;

MySQLContainer<?> mysql = new MySQLContainer<>(
    new ImageFromDockerfile()
        .withDockerfile(Paths.get("Dockerfile.mysql-custom"))
);

mysql.start();

Debugging Tips

Enable Query Logging

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;

MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
    .withCommand("mysqld", "--general_log=1", "--log_output=TABLE");

mysql.start();

// View queries: SELECT * FROM mysql.general_log;

Inspect Container Details

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
import com.github.dockerjava.api.command.InspectContainerResponse;

MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"));
mysql.start();

InspectContainerResponse containerInfo = mysql.getContainerInfo();
System.out.println("Container ID: " + containerInfo.getId());
System.out.println("Container Name: " + containerInfo.getName());
System.out.println("Network Settings: " + containerInfo.getNetworkSettings());

Execute Commands in Running Container

import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.containers.Container.ExecResult;

MySQLContainer<?> mysql = new MySQLContainer<>(DockerImageName.parse("mysql:8.0"));
mysql.start();

// Execute command in container
ExecResult result = mysql.execInContainer("mysql", "--version");
System.out.println("MySQL Version: " + result.getStdout());

Best Practices

  1. Use Log Consumers for Debugging: Enable logging when diagnosing container issues
  2. Set Appropriate Timeouts: Adjust based on environment (local vs CI)
  3. Leverage Environment Variables: Use env vars for configuration over command overrides when possible
  4. Document Custom Configurations: Explain why specific customizations were made
  5. Use Networks for Multi-Container Tests: Enable container-to-container communication
  6. Monitor Resource Usage: Be aware of memory and CPU limits
  7. Clean Up Containers: Use try-with-resources or ensure stop() is called
  8. Test Customizations Individually: Verify each customization works before combining
  9. Use Port Mapping Correctly: Always use getMappedPort() instead of assuming port numbers
  10. Enable Logging in CI: Use log consumers to capture issues in CI environments
  11. Call Methods Before Start: All customization methods must be called before start()
  12. Check Container State: Verify container is started before calling information methods