Generic Podman container runtime
Project description
podman-runner · Simple, safe Podman containers for Python tests and scripts
podman-runner brings the convenience of testcontainers to the rootless, daemonless world of Podman.
Start containers, wait for them to be ready, run commands, mount init scripts, map ports — all with a clean context-manager API and automatic cleanup.
Perfect for integration tests, local development scripts, or anything that needs a real database/service without Docker.
from podman_runner import Container, ContainerConfig
with Container(
ContainerConfig(
name="my-postgres",
image="docker.io/library/postgres:16-alpine",
env={"POSTGRES_PASSWORD": "secret"},
health_cmd=["pg_isready", "-U", "postgres"],
)
) as pg:
pg.exec(["psql", "-U", "postgres", "-c", "CREATE TABLE test(id serial);"])
# → container stops and is removed automatically
Features
- Context manager → guaranteed cleanup (even on exceptions or Ctrl-C)
- Automatic health-check polling (
health_cmd) - Smart port mapping (fixed or dynamic)
- Auto-mount init scripts into
/docker-entrypoint-initdb.d-style directories - Full volume and environment support
- Detailed pre-flight checks that catch the most common Podman pitfalls (WSL shm, Docker conflict, socket not running, Snap sandbox, etc.)
- 100% type-annotated, fully tested, zero external runtime dependencies
Installation
Requires Podman ≥ 4.0 and Python ≥ 3.11.
pip install podman-runner
Or with the fast uv tool (recommended):
uv pip install podman-runner
Install Podman
# Ubuntu/Debian
sudo apt install podman
# Fedora
sudo dnf install podman
# macOS (via Homebrew)
brew install podman
podman machine init
podman machine start
# Windows (WSL2 recommended)
# See https://podman.io/getting-started/install.html
Quick Examples
Basic container
from podman_runner import Container, ContainerConfig
with Container(ContainerConfig(
name="demo",
image="docker.io/library/alpine:latest",
command=["sleep", "infinity"],
)) as c:
print(c.exec(["echo", "Hello Podman!"]).stdout)
PostgreSQL with init scripts
from pathlib import Path
from podman_runner import Container, ContainerConfig
script = Path("01-setup.sql")
script.write_text("CREATE TABLE users(id serial PRIMARY KEY);\n")
with Container(ContainerConfig(
name="pg-init",
image="docker.io/library/postgres:16-alpine",
env={"POSTGRES_PASSWORD": "secret"},
init_dir="/docker-entrypoint-initdb.d",
init_scripts=[script],
health_cmd=["pg_isready", "-U", "postgres"],
)) as pg:
result = pg.exec(["psql", "-U", "postgres", "-c", "\\dt"])
print(result.stdout)
Redis with health check
with Container(ContainerConfig(
name="redis",
image="docker.io/library/redis:alpine",
health_cmd=["redis-cli", "ping"],
)) as r:
assert r.exec(["redis-cli", "PING"]).stdout.strip() == "PONG"
Nginx with port mapping
import requests
with Container(ContainerConfig(
name="web",
image="docker.io/library/nginx:alpine",
ports={80: 8080}, # fixed host port
# ports={443: None}, # auto-assign free host port
)) as web:
host_port = web.get_port(80)
resp = requests.get(f"http://localhost:{host_port}")
assert resp.status_code == 200
See the examples/ directory for more.
API Reference
ContainerConfig
| Parameter | Type | Description |
|---|---|---|
name |
str |
Unique container name (required) |
image |
str |
Image to run (required) |
command |
list[str] | None |
Override container command |
env |
dict[str, str] | None |
Environment variables |
ports |
dict[int, int | None] | None |
{container_port: host_port_or_None} — None = auto-assign |
volumes |
dict[Path, str] | None |
Host path → container path (:ro suffix for read-only) |
init_dir |
str | None |
Target init directory (e.g. "/docker-entrypoint-initdb.d") |
init_scripts |
list[Path] | None |
Scripts auto-mounted with 00-, 01- prefix and :ro |
health_cmd |
list[str] | None |
Command that exits 0 when service is ready |
health_timeout |
int |
Seconds to wait for health check (default: 30) |
health_interval |
float |
Seconds between health checks (default: 1.0) |
Container
| Method | Description |
|---|---|
container_id: str | None |
Podman container ID after start |
start() |
Start container (called automatically in context manager) |
stop() |
Stop and remove container |
exec(cmd: list[str]) |
Run command inside container → CompletedProcess |
logs(tail: int | None = None, follow=False) |
Get container logs |
get_port(internal: int) |
Return host port mapped to container port (or None) |
check_status() |
Return current status string ("running", "exited", etc.) |
Development
git clone https://github.com/Francois-NT/podman-runner.git
cd podman-runner
# Install with dev dependencies
uv sync --all-extras
# Install pre-commit hooks
uv run pre-commit install
# Run tests
uv run task unittest # fast unit tests
uv run task integration # requires running Podman socket
Why Podman instead of Docker?
- No daemon → works great in CI and restricted environments
- Rootless by default
- Full Docker CLI compatibility
- Native on Linux, excellent macOS/Windows support via
podman machine
Similar Projects
| Project | Docker | Podman | Context Manager | Init Script Support | Health Polling |
|---|---|---|---|---|---|
| testcontainers-python | Yes | No | Yes | No | Yes |
| podman-compose | No | Yes | No | No | No |
| podman-py | No | Yes | No | No | No |
| podman-runner | No | Yes | Yes | Yes | Yes |
License
MIT © François Naggar-Tremblay
Happy testing with real, rootless containers!
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file podman_runner-0.1.0.tar.gz.
File metadata
- Download URL: podman_runner-0.1.0.tar.gz
- Upload date:
- Size: 8.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87cbc1c51c87095d253fe562847c35efbfcfa71a08416ea32e41ea1d6e822259
|
|
| MD5 |
e587213550a275ccc42e70a33d0e96e5
|
|
| BLAKE2b-256 |
110dfb4b2db8828682602ebe61a55a798c016d1ab95c6259a79072e6bb6e4c27
|
File details
Details for the file podman_runner-0.1.0-py3-none-any.whl.
File metadata
- Download URL: podman_runner-0.1.0-py3-none-any.whl
- Upload date:
- Size: 9.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ac12e9e9600067bf1de8b99d4c5e932e6b384826477228e5a9c725615af0a792
|
|
| MD5 |
e187321f4b702b7eb6ef30470629e9d2
|
|
| BLAKE2b-256 |
5b62b8c9b69a6f68d68c38792606517f67fa248d189018026fd01df8e0db9bc1
|