Testcontainers-Python provides several ways to execute commands inside containers. This is useful for setup, verification, and debugging during tests.
The simplest way to execute a command is using the exec method, passing either an argv list or a string:
from testcontainers.core.container import DockerContainer
with DockerContainer("alpine:latest") as container:
# Execute a simple command (argv form)
result = container.exec(["ls", "-la"])
print(result.exit_code) # 0
print(result.output) # command output as bytes
print(result.output.decode()) # ...decoded to str
# A string is also accepted
result = container.exec("ls -la")exec returns a named (exit_code, output) tuple, so you can also unpack it directly:
exit_code, output = container.exec(["ls", "-la"])A string command is not run through a shell.
docker-pytokenizes it withshlex.split, so shell features such as pipes, redirections, and variable expansion are passed through literally —container.exec("echo $HOME")prints the text$HOME, not your home directory. When you need shell behavior, invoke a shell explicitly:container.exec(["sh", "-c", "echo $HOME"]).
To customize how a command runs — the user, environment, or working directory — pass an ExecConfig:
from testcontainers.core.container import DockerContainer, ExecConfig
with DockerContainer("alpine:latest") as container:
# Execute command as a specific user
result = container.exec(ExecConfig(command=["whoami"], user="nobody"))
# Execute command with environment variables
# (use a command that reads the environment, e.g. printenv -- a bare
# argv command is not shell-expanded, see the note above)
result = container.exec(ExecConfig(command=["printenv", "TEST_VAR"], environment={"TEST_VAR": "test_value"}))
# Execute command in a working directory (str or pathlib.Path)
result = container.exec(ExecConfig(command=["pwd"], workdir="/tmp"))ExecConfig is a frozen dataclass: only command is required, and user, environment, workdir, and privileged are optional. Because it is immutable, the idiomatic way to derive a variant is dataclasses.replace:
from dataclasses import replace
base = ExecConfig(command=["pwd"], workdir="/tmp")
in_var = replace(base, workdir="/var")For commands that require elevated privileges, set privileged=True:
from testcontainers.core.container import DockerContainer, ExecConfig
with DockerContainer("alpine:latest") as container:
# Execute command with privileges
result = container.exec(ExecConfig(command=["mount"], privileged=True))- Handle command failures gracefully — check
exit_coderather than assuming success - Use environment variables for configuration
- Consider security implications of privileged commands
- Clean up after command execution
- Use appropriate user permissions
- Decode
outputfrom bytes when you need text - Use an explicit
["sh", "-c", ...]invocation for commands that rely on shell features
from testcontainers.community.postgres import PostgresContainer
with PostgresContainer() as postgres:
# Create a database
postgres.exec(["createdb", "testdb"])
# Run migrations
postgres.exec(["psql", "-d", "testdb", "-f", "/path/to/migrations.sql"])from testcontainers.core.container import DockerContainer
with DockerContainer("alpine:latest") as container:
# Create a directory
container.exec(["mkdir", "-p", "/data"])
# Set permissions
container.exec(["chmod", "755", "/data"])
# List files
result = container.exec(["ls", "-la", "/data"])from testcontainers.core.container import DockerContainer
with DockerContainer("nginx:alpine") as container:
# Check service status
result = container.exec(["nginx", "-t"])
# Reload configuration
container.exec(["nginx", "-s", "reload"])If you encounter issues with command execution:
- Check command syntax and arguments
- Verify user permissions
- Check container state
- Verify command availability
- Verify environment variables
- Check the working directory
- Remember that string commands are tokenized, not shell-interpreted