# Docker Rust Libraries Comparison Guide
This guide helps you choose the right Docker library for your Rust project by comparing docker-wrapper with other popular options like bollard, testcontainers-rs, and raw Docker CLI usage.
## Quick Decision Tree
```
Need containers in Rust?
├── Just for integration tests? → testcontainers-rs
├── Building a container platform/orchestrator? → bollard
├── Building CLI tools or automation scripts? → docker-wrapper
├── Need subprocess control & familiar CLI behavior? → docker-wrapper
├── Maximum performance for production services? → bollard
├── Want simple Docker-CLI-like API? → docker-wrapper
└── Already have shell scripts to port? → docker-wrapper
```
## Library Comparison Matrix
| **Approach** | CLI subprocess wrapper | Direct Docker API client | Test-focused abstraction | Shell commands |
| **Learning Curve** | Low (Docker CLI knowledge) | Medium (Docker API knowledge) | Low (test-focused) | High (error handling) |
| **Dependencies** | Docker CLI required | None (direct HTTP/socket) | Docker CLI required | Docker CLI required |
| **Performance** | Medium (subprocess overhead) | High (direct API) | Medium | Low (manual parsing) |
| **Feature Coverage** | Complete CLI feature set | Complete API feature set | Test-focused subset | Complete CLI feature set |
| **Error Handling** | Docker CLI error messages | Raw API error responses | Test-friendly errors | Manual parsing required |
| **Compatibility** | Follows Docker CLI changes | Follows API versions | Limited scope | Manual maintenance |
| **Async Support** | Full tokio support | Native async | Test-focused async | Manual implementation |
| **Streaming** | Output streaming | Advanced streaming | Limited | Complex to implement |
| **Production Ready** | Yes | Yes | No (test-focused) | Depends on implementation |
## Use Case Recommendations
### Choose docker-wrapper when:
- **Building CLI tools** or automation scripts
- **Developer tools** that manage containers
- **CI/CD pipelines** and deployment automation
- **Docker Compose** integration is needed
- **Familiar CLI behavior** is important
- **Cross-runtime support** (Docker + Podman) is needed
- **Prototyping** or **scripting** container workflows
- **Migrating from shell scripts** to Rust
- **Simple container management** for applications
**Example use cases:**
- Development environment setup tools
- Deployment automation scripts
- Container management CLIs
- Docker Compose workflow automation
- Multi-environment deployment tools
### Choose bollard when:
- **Building container platforms** or orchestrators
- **Production services** managing many containers
- **High-performance** container operations
- **Fine-grained control** over Docker daemon
- **Streaming logs/events** from many containers
- **Container runtime integration** in services
- **Low-level Docker API** access needed
**Example use cases:**
- Container orchestration platforms
- Monitoring and observability tools
- Container-as-a-Service platforms
- CI/CD runners (like GitHub Actions)
- Container security scanning tools
### Choose testcontainers-rs when:
- **Integration testing** is the primary use case
- **Database/service testing** with real containers
- **Test isolation** and **automatic cleanup** needed
- **Pre-built modules** for common services
- **JUnit/TestNG-style** container lifecycle
**Example use cases:**
- Integration tests for microservices
- Database-dependent tests
- End-to-end testing scenarios
- API testing with external dependencies
### Avoid raw Docker CLI when:
- You need robust error handling
- Performance is important
- Cross-platform compatibility matters
- You want type safety and good APIs
## Performance Comparison
| **Startup overhead** | Higher (subprocess) | Lower (HTTP connection) | Higher (abstraction layer) | Per command |
| **Bulk operations** | Slower (process spawning) | Fastest (direct API) | Medium (optimized for tests) | Many containers |
| **Memory usage** | Lower (subprocess cleanup) | Medium (connection pools) | Medium (test lifecycle) | Runtime footprint |
| **CPU usage** | Higher (process spawning) | Lower (HTTP calls) | Medium | Sustained operations |
| **Concurrent operations** | Limited (process pools) | Excellent (async HTTP) | Good (parallel tests) | Scalability |
## Code Comparison Examples
### Starting a Container
#### docker-wrapper
```rust
use docker_wrapper::RunCommand;
let result = RunCommand::new("redis:latest")
.name("my-redis")
.detach()
.port(6379, 6379)
.env("REDIS_PASSWORD", "secret")
.run()
.await?;
println!("Container ID: {}", result.container_id());
```
#### bollard
```rust
use bollard::{Docker, container::{Config, CreateContainerOptions}};
use bollard::service::{HostConfig, PortBinding};
use std::collections::HashMap;
let docker = Docker::connect_with_local_defaults()?;
let config = Config {
image: Some("redis:latest"),
env: Some(vec!["REDIS_PASSWORD=secret"]),
host_config: Some(HostConfig {
port_bindings: Some(HashMap::from([(
"6379/tcp".to_string(),
Some(vec![PortBinding {
host_port: Some("6379".to_string()),
..Default::default()
}])
)])),
..Default::default()
}),
..Default::default()
};
let container = docker.create_container(
Some(CreateContainerOptions {
name: "my-redis",
platform: None,
}),
config
).await?;
docker.start_container::<String>(&container.id, None).await?;
```
#### testcontainers-rs
```rust
use testcontainers::{runners::AsyncRunner, GenericImage};
let container = GenericImage::new("redis", "latest")
.with_exposed_port(6379)
.start()
.await?;
let port = container.get_host_port_ipv4(6379)?;
```
### Key Differences
1. **docker-wrapper**: Mirrors Docker CLI exactly - familiar and predictable
2. **bollard**: Requires understanding Docker API structure - more verbose but flexible
3. **testcontainers**: Optimized for testing - simple but limited scope
## Feature Deep Dive
### Docker Compose Support
| **docker-wrapper** | Full native support | Direct `docker compose` commands |
| **bollard** | No support | API doesn't include Compose |
| **testcontainers-rs** | Limited | Some compose support for tests |
| **Raw CLI** | Full support | Manual `docker-compose` calls |
### Cross-Platform Runtime Support
| **docker-wrapper** | Yes | Yes | Yes | Yes (any Docker CLI compatible) |
| **bollard** | Yes | Yes (with socket setup) | Yes | Yes (if API compatible) |
| **testcontainers-rs** | Yes | Yes (with configuration) | Yes | Depends on Docker compatibility |
*Note: Podman support typically requires Docker compatibility mode enabled*
### Error Handling Quality
#### docker-wrapper
```rust
// User-friendly Docker CLI error messages
Error: Container 'my-app' is already running
Error: Image 'nginx:invalid-tag' not found
Error: Port 80 is already allocated
```
#### bollard
```rust
// Raw API error responses
Error: API responded with status 409: Conflict
Error: API responded with status 404: Not Found
Error: API responded with status 400: Bad Request
```
## Migration Guides
### From Shell Scripts to docker-wrapper
**Before (Bash):**
```bash
#!/bin/bash
docker run -d --name redis \
-p 6379:6379 \
-e REDIS_PASSWORD=secret \
redis:latest
if [ $? -eq 0 ]; then
echo "Redis started successfully"
else
echo "Failed to start Redis"
exit 1
fi
```
**After (Rust with docker-wrapper):**
```rust
use docker_wrapper::RunCommand;
let result = RunCommand::new("redis:latest")
.name("redis")
.detach()
.port(6379, 6379)
.env("REDIS_PASSWORD", "secret")
.run()
.await?;
if result.is_success() {
println!("Redis started successfully");
} else {
eprintln!("Failed to start Redis");
return Err(result.error())?;
}
```
### From testcontainers-rs to docker-wrapper
**Before (testcontainers-rs):**
```rust
#[tokio::test]
async fn test_redis_connection() {
use testcontainers::{runners::AsyncRunner, GenericImage};
let container = GenericImage::new("redis", "latest")
.with_exposed_port(6379)
.start()
.await?;
let port = container.get_host_port_ipv4(6379)?;
// Test logic here
// Container automatically cleaned up
}
```
**After (docker-wrapper):**
```rust
#[tokio::test]
async fn test_redis_connection() {
let container = RunCommand::new("redis:latest")
.name("test-redis")
.port(6379, 0) // Random host port
.detach()
.remove() // Auto-remove when stopped
.run()
.await?;
let port = container.port_mapping(6379)?;
// Test logic here
StopCommand::new("test-redis").run().await?;
// Container auto-removed due to --rm flag
}
```
### From bollard to docker-wrapper
**Before (bollard):**
```rust
use bollard::{Docker, container::ListContainersOptions};
use std::collections::HashMap;
let docker = Docker::connect_with_local_defaults()?;
let containers = docker.list_containers(Some(ListContainersOptions::<String> {
all: true,
filters: HashMap::from([
("status".to_string(), vec!["running".to_string()])
]),
..Default::default()
})).await?;
for container in containers {
println!("Container: {} ({})",
container.names.unwrap_or_default().join(","),
container.id.unwrap_or_default()
);
}
```
**After (docker-wrapper):**
```rust
let result = PsCommand::new()
.all()
.filter("status", "running")
.run()
.await?;
for container in result.containers() {
println!("Container: {} ({})",
container.name(),
container.id()
);
}
```
## Real-World Examples
### Example 1: Development Environment Setup Tool
**Scenario**: CLI tool to set up development environments with databases and services.
**Why docker-wrapper?**
- Users expect Docker CLI-like behavior
- Need Docker Compose support for complex stacks
- Cross-platform compatibility (developers use different Docker setups)
- Error messages should be familiar to Docker users
```rust
use docker_wrapper::{RunCommand, ComposeUpCommand};
// Start individual services
let postgres = RunCommand::new("postgres:13")
.name("dev-postgres")
.env("POSTGRES_PASSWORD", "dev")
.port(5432, 5432)
.detach()
.run().await?;
// Or start entire stack with Compose
let stack = ComposeUpCommand::new()
.file("docker-compose.dev.yml")
.detach()
.run().await?;
```
### Example 2: CI/CD Pipeline Container Management
**Scenario**: Build system that runs tests in isolated containers.
**Why docker-wrapper?**
- Familiar Docker CLI commands for DevOps teams
- Need reliable subprocess control
- Integration with existing Docker-based build processes
- Simple container lifecycle management
```rust
use docker_wrapper::{RunCommand, BuildCommand};
// Build test image
let build = BuildCommand::new(".")
.tag("app:test")
.run().await?;
// Run tests in container
let test_result = RunCommand::new("app:test")
.cmd(vec!["cargo".to_string(), "test".to_string()])
.volume("./target:/app/target")
.remove() // Clean up after test
.run().await?;
if !test_result.is_success() {
return Err("Tests failed".into());
}
```
### Example 3: Container Monitoring Platform
**Scenario**: Production service that monitors and manages hundreds of containers.
**Why bollard?**
- High performance for many concurrent operations
- Real-time event streaming from Docker daemon
- Fine-grained control over container lifecycle
- Integration into long-running service architecture
```rust
use bollard::{Docker, system::{EventsOptions, EventMessage}};
use futures::stream::StreamExt;
use std::collections::HashMap;
let docker = Docker::connect_with_local_defaults()?;
// Stream container events in real-time
let mut events = docker.events(Some(EventsOptions::<String> {
filters: HashMap::from([
("type".to_string(), vec!["container".to_string()])
]),
..Default::default()
}));
while let Some(event) = events.next().await {
match event? {
EventMessage {
action: Some(action),
actor: Some(actor),
..
} => {
println!("Container {} {}",
actor.id.unwrap_or_default(),
action
);
// Handle container lifecycle events
}
_ => {}
}
}
```
## Performance Characteristics
### Expected Performance Trade-offs
**docker-wrapper**
- **Startup**: Higher latency due to subprocess spawning
- **Throughput**: Limited by process creation overhead
- **Memory**: Lower baseline, processes cleaned up after commands
- **Concurrency**: Constrained by system process limits
**bollard**
- **Startup**: Lower latency with direct HTTP API calls
- **Throughput**: Higher throughput with persistent connections
- **Memory**: Higher baseline due to connection pools and caching
- **Concurrency**: Excellent async performance
**testcontainers-rs**
- **Startup**: Higher latency due to test framework abstractions
- **Throughput**: Optimized for test scenarios, not production workloads
- **Memory**: Test lifecycle management adds overhead
- **Concurrency**: Good for parallel test execution
*Note: Actual performance will vary significantly based on your specific use case, system configuration, and Docker setup. Consider benchmarking with your own workload for accurate comparisons.*
## Summary
### Choose docker-wrapper for:
- CLI tools and automation scripts
- Docker Compose workflows
- Rapid prototyping and development
- Familiar Docker CLI behavior
- Cross-platform Docker runtime support
### Choose bollard for:
- Container platforms and orchestrators
- High-performance production services
- Real-time monitoring and events
- Fine-grained Docker API control
- Scalable container management
### Choose testcontainers-rs for:
- Integration testing scenarios
- Database-dependent tests
- Test isolation and cleanup
- Pre-built service modules
- Quick test environment setup
---
*This comparison is maintained by the docker-wrapper community. For corrections or updates, please open an issue or pull request.*