docker_wrapper/
lib.rs

1//! # docker-wrapper
2//!
3//! A type-safe Docker CLI wrapper for Rust.
4//!
5//! This crate provides an idiomatic Rust interface to the Docker command-line tool.
6//! All commands use a builder pattern and async execution via Tokio.
7//!
8//! # Quick Start
9//!
10//! ```rust,no_run
11//! use docker_wrapper::{DockerCommand, RunCommand, StopCommand, RmCommand};
12//!
13//! #[tokio::main]
14//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
15//!     // Run a container
16//!     let container = RunCommand::new("nginx:alpine")
17//!         .name("my-nginx")
18//!         .port(8080, 80)
19//!         .detach()
20//!         .execute()
21//!         .await?;
22//!
23//!     println!("Started: {}", container.short());
24//!
25//!     // Stop and remove
26//!     StopCommand::new("my-nginx").execute().await?;
27//!     RmCommand::new("my-nginx").execute().await?;
28//!
29//!     Ok(())
30//! }
31//! ```
32//!
33//! # Core Concepts
34//!
35//! ## The `DockerCommand` Trait
36//!
37//! All commands implement [`DockerCommand`], which provides the [`execute()`](DockerCommand::execute)
38//! method. You must import this trait to call `.execute()`:
39//!
40//! ```rust
41//! use docker_wrapper::DockerCommand; // Required for .execute()
42//! ```
43//!
44//! ## Builder Pattern
45//!
46//! Commands are configured using method chaining:
47//!
48//! ```rust,no_run
49//! # use docker_wrapper::{DockerCommand, RunCommand};
50//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
51//! RunCommand::new("alpine")
52//!     .name("my-container")
53//!     .env("DATABASE_URL", "postgres://localhost/db")
54//!     .volume("/data", "/app/data")
55//!     .port(3000, 3000)
56//!     .memory("512m")
57//!     .cpus("0.5")
58//!     .detach()
59//!     .rm()  // Auto-remove when stopped
60//!     .execute()
61//!     .await?;
62//! # Ok(())
63//! # }
64//! ```
65//!
66//! ## Error Handling
67//!
68//! All commands return `Result<T, docker_wrapper::Error>`:
69//!
70//! ```rust,no_run
71//! # use docker_wrapper::{DockerCommand, RunCommand, Error};
72//! # async fn example() {
73//! match RunCommand::new("nginx").detach().execute().await {
74//!     Ok(id) => println!("Started: {}", id.short()),
75//!     Err(Error::CommandFailed { stderr, .. }) => {
76//!         eprintln!("Docker error: {}", stderr);
77//!     }
78//!     Err(e) => eprintln!("Error: {}", e),
79//! }
80//! # }
81//! ```
82//!
83//! # Command Categories
84//!
85//! ## Container Lifecycle
86//!
87//! ```rust,no_run
88//! use docker_wrapper::{
89//!     DockerCommand,
90//!     RunCommand,      // docker run
91//!     CreateCommand,   // docker create
92//!     StartCommand,    // docker start
93//!     StopCommand,     // docker stop
94//!     RestartCommand,  // docker restart
95//!     KillCommand,     // docker kill
96//!     RmCommand,       // docker rm
97//!     PauseCommand,    // docker pause
98//!     UnpauseCommand,  // docker unpause
99//! };
100//! ```
101//!
102//! ## Container Inspection
103//!
104//! ```rust,no_run
105//! use docker_wrapper::{
106//!     DockerCommand,
107//!     PsCommand,       // docker ps
108//!     LogsCommand,     // docker logs
109//!     InspectCommand,  // docker inspect
110//!     TopCommand,      // docker top
111//!     StatsCommand,    // docker stats
112//!     PortCommand,     // docker port
113//!     DiffCommand,     // docker diff
114//! };
115//! ```
116//!
117//! ## Container Operations
118//!
119//! ```rust,no_run
120//! use docker_wrapper::{
121//!     DockerCommand,
122//!     ExecCommand,     // docker exec
123//!     AttachCommand,   // docker attach
124//!     CpCommand,       // docker cp
125//!     WaitCommand,     // docker wait
126//!     RenameCommand,   // docker rename
127//!     UpdateCommand,   // docker update
128//!     CommitCommand,   // docker commit
129//!     ExportCommand,   // docker export
130//! };
131//! ```
132//!
133//! ## Images
134//!
135//! ```rust,no_run
136//! use docker_wrapper::{
137//!     DockerCommand,
138//!     ImagesCommand,   // docker images
139//!     PullCommand,     // docker pull
140//!     PushCommand,     // docker push
141//!     BuildCommand,    // docker build
142//!     TagCommand,      // docker tag
143//!     RmiCommand,      // docker rmi
144//!     SaveCommand,     // docker save
145//!     LoadCommand,     // docker load
146//!     ImportCommand,   // docker import
147//!     HistoryCommand,  // docker history
148//!     SearchCommand,   // docker search
149//! };
150//! ```
151//!
152//! ## Networks and Volumes
153//!
154//! ```rust,no_run
155//! use docker_wrapper::{
156//!     DockerCommand,
157//!     NetworkCreateCommand, NetworkLsCommand, NetworkRmCommand,
158//!     VolumeCreateCommand, VolumeLsCommand, VolumeRmCommand,
159//! };
160//! ```
161//!
162//! ## System
163//!
164//! ```rust,no_run
165//! use docker_wrapper::{
166//!     DockerCommand,
167//!     VersionCommand,  // docker version
168//!     InfoCommand,     // docker info
169//!     EventsCommand,   // docker events
170//!     LoginCommand,    // docker login
171//!     LogoutCommand,   // docker logout
172//!     SystemDfCommand, // docker system df
173//!     SystemPruneCommand, // docker system prune
174//! };
175//! ```
176//!
177//! # Feature Flags
178//!
179//! ## `compose` - Docker Compose Support
180//!
181//! ```toml
182//! docker-wrapper = { version = "0.10", features = ["compose"] }
183//! ```
184//!
185//! ```rust,no_run
186//! # #[cfg(feature = "compose")]
187//! use docker_wrapper::{DockerCommand, compose::{ComposeUpCommand, ComposeDownCommand, ComposeCommand}};
188//!
189//! # #[cfg(feature = "compose")]
190//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
191//! // Start services
192//! ComposeUpCommand::new()
193//!     .file("docker-compose.yml")
194//!     .detach()
195//!     .execute()
196//!     .await?;
197//!
198//! // Stop and clean up
199//! ComposeDownCommand::new()
200//!     .volumes()
201//!     .execute()
202//!     .await?;
203//! # Ok(())
204//! # }
205//! ```
206//!
207//! ## `templates` - Pre-configured Containers
208//!
209//! ```toml
210//! docker-wrapper = { version = "0.10", features = ["templates"] }
211//! ```
212//!
213//! Templates provide ready-to-use configurations for common services:
214//!
215//! ```rust,no_run
216//! # #[cfg(feature = "template-redis")]
217//! use docker_wrapper::{RedisTemplate, Template};
218//!
219//! # #[cfg(feature = "template-redis")]
220//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
221//! let redis = RedisTemplate::new("my-redis")
222//!     .port(6379)
223//!     .password("secret")
224//!     .with_persistence("redis-data");
225//!
226//! let id = redis.start().await?;
227//! // ... use Redis ...
228//! redis.stop().await?;
229//! # Ok(())
230//! # }
231//! ```
232//!
233//! Available templates:
234//! - [`RedisTemplate`], [`RedisSentinelTemplate`], [`RedisClusterTemplate`]
235//! - [`PostgresTemplate`], [`MysqlTemplate`], [`MongodbTemplate`]
236//! - [`NginxTemplate`]
237//!
238//! ## `swarm` - Docker Swarm Commands
239//!
240//! ```toml
241//! docker-wrapper = { version = "0.10", features = ["swarm"] }
242//! ```
243//!
244//! ## `manifest` - Multi-arch Manifest Commands
245//!
246//! ```toml
247//! docker-wrapper = { version = "0.10", features = ["manifest"] }
248//! ```
249//!
250//! # Tracing and Debugging
251//!
252//! docker-wrapper integrates with the [`tracing`](https://docs.rs/tracing) ecosystem
253//! to provide comprehensive observability into Docker command execution.
254//!
255//! ## Enabling Tracing
256//!
257//! Add tracing dependencies to your project:
258//!
259//! ```toml
260//! [dependencies]
261//! tracing = "0.1"
262//! tracing-subscriber = { version = "0.3", features = ["env-filter"] }
263//! ```
264//!
265//! Initialize a subscriber in your application:
266//!
267//! ```rust,ignore
268//! use tracing_subscriber::EnvFilter;
269//!
270//! tracing_subscriber::fmt()
271//!     .with_env_filter(EnvFilter::from_default_env())
272//!     .init();
273//!
274//! // Your application code...
275//! ```
276//!
277//! ## Log Levels
278//!
279//! Control verbosity with the `RUST_LOG` environment variable:
280//!
281//! ```bash
282//! # Show all docker-wrapper traces
283//! RUST_LOG=docker_wrapper=trace cargo run
284//!
285//! # Show only command execution info
286//! RUST_LOG=docker_wrapper::command=debug cargo run
287//!
288//! # Show template lifecycle events
289//! RUST_LOG=docker_wrapper::template=debug cargo run
290//!
291//! # Show retry/debug executor activity
292//! RUST_LOG=docker_wrapper::debug=trace cargo run
293//! ```
294//!
295//! ## What Gets Traced
296//!
297//! The library instruments key operations at various levels:
298//!
299//! - **`trace`**: Command arguments, stdout/stderr output, retry delays
300//! - **`debug`**: Command start/completion, health check attempts, retry attempts
301//! - **`info`**: Container lifecycle events (start, stop, ready)
302//! - **`warn`**: Health check failures, retry exhaustion warnings
303//! - **`error`**: Command failures, timeout errors
304//!
305//! ## Instrumented Operations
306//!
307//! ### Command Execution
308//! All Docker commands are traced with:
309//! - Command name and runtime (docker/podman)
310//! - Timeout configuration
311//! - Exit status and duration
312//! - Stdout/stderr output (at trace level)
313//!
314//! ### Template Lifecycle
315//! Template operations include:
316//! - Container start with image and configuration
317//! - Health check polling with attempt counts
318//! - Ready state transitions
319//! - Stop and remove operations
320//!
321//! ### Retry Logic
322//! The debug executor traces:
323//! - Retry policy configuration
324//! - Individual retry attempts with delays
325//! - Backoff calculations
326//! - Final success or exhaustion
327//!
328//! ## Example Output
329//!
330//! With `RUST_LOG=docker_wrapper=debug`:
331//!
332//! ```text
333//! DEBUG docker.command{command="run" runtime="docker"}: starting command
334//! DEBUG docker.command{command="run"}: command completed exit_code=0
335//! INFO  template.start{name="my-redis" image="redis:7-alpine"}: container started
336//! DEBUG template.wait{name="my-redis"}: health check attempt=1 elapsed_ms=50
337//! INFO  template.wait{name="my-redis"}: container ready after 1 attempts
338//! ```
339//!
340//! # Streaming Output
341//!
342//! For long-running commands, stream output in real-time:
343//!
344//! ```rust,no_run
345//! use docker_wrapper::{BuildCommand, StreamHandler, StreamableCommand};
346//!
347//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
348//! let result = BuildCommand::new(".")
349//!     .tag("my-app:latest")
350//!     .stream(StreamHandler::print())
351//!     .await?;
352//!
353//! if result.is_success() {
354//!     println!("Build complete!");
355//! }
356//! # Ok(())
357//! # }
358//! ```
359//!
360//! # Checking Docker Availability
361//!
362//! ```rust,no_run
363//! use docker_wrapper::ensure_docker;
364//!
365//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
366//! let info = ensure_docker().await?;
367//! println!("Docker {}.{}.{}", info.version.major, info.version.minor, info.version.patch);
368//! # Ok(())
369//! # }
370//! ```
371//!
372//! # Why docker-wrapper?
373//!
374//! This crate wraps the Docker CLI rather than calling the Docker API directly
375//! (like [bollard](https://crates.io/crates/bollard)).
376//!
377//! **docker-wrapper advantages:**
378//! - Just needs `docker` in PATH (no socket access required)
379//! - Native `docker compose` support
380//! - Works with Docker, Podman, Colima, and other Docker-compatible CLIs
381//! - Familiar mental model if you know Docker CLI
382//!
383//! **bollard advantages:**
384//! - Direct API calls (no process spawn overhead)
385//! - Lower latency for high-frequency operations
386//! - No external binary dependency
387//!
388//! **Choose docker-wrapper** for CLI tools, dev tooling, Compose workflows, or
389//! when working with Docker alternatives.
390//!
391//! **Choose bollard** for high-performance services with many Docker operations.
392
393#![warn(missing_docs)]
394#![warn(clippy::all)]
395#![warn(clippy::pedantic)]
396
397pub mod command;
398#[cfg(feature = "compose")]
399pub mod compose;
400pub mod debug;
401pub mod error;
402pub mod platform;
403pub mod prerequisites;
404pub mod stream;
405
406#[cfg(any(
407    feature = "templates",
408    feature = "template-redis",
409    feature = "template-redis-cluster",
410    feature = "template-redis-enterprise",
411    feature = "template-postgres",
412    feature = "template-mysql",
413    feature = "template-mongodb",
414    feature = "template-nginx"
415))]
416/// Container templates module
417///
418/// Provides pre-configured container templates with sensible defaults for common services.
419/// Templates support custom images, platforms, persistence, and resource configuration.
420///
421/// See the [Template Guide](https://github.com/joshrotenberg/docker-wrapper/blob/main/docs/TEMPLATES.md) for comprehensive documentation.
422///
423/// # Available Templates
424///
425/// ## Redis Templates
426/// - [`RedisTemplate`] - Basic Redis server
427/// - [`RedisSentinelTemplate`] - High-availability Redis with Sentinel
428/// - [`RedisClusterTemplate`] - Sharded Redis cluster
429/// - [`RedisEnterpriseTemplate`] - Redis Enterprise with management
430/// - [`RedisInsightTemplate`] - Redis management UI
431///
432/// ## Database Templates
433/// - [`PostgresTemplate`] - PostgreSQL database
434/// - [`MysqlTemplate`] - MySQL database
435/// - [`MongodbTemplate`] - MongoDB document database
436///
437/// ## Web Server Templates
438/// - [`NginxTemplate`] - Nginx web server
439///
440/// # Quick Start
441///
442/// ```rust,no_run
443/// use docker_wrapper::{RedisTemplate, Template};
444///
445/// # #[tokio::main]
446/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
447/// let redis = RedisTemplate::new("my-redis")
448///     .port(6379)
449///     .password("secret")
450///     .with_persistence("redis-data");
451///
452/// let container_id = redis.start().await?;
453/// println!("Redis started: {}", container_id);
454/// # Ok(())
455/// # }
456/// ```
457#[cfg(any(
458    feature = "templates",
459    feature = "template-redis",
460    feature = "template-redis-cluster",
461    feature = "template-redis-enterprise",
462    feature = "template-postgres",
463    feature = "template-mysql",
464    feature = "template-mongodb",
465    feature = "template-nginx"
466))]
467pub mod template;
468#[cfg(feature = "testing")]
469pub mod testing;
470
471pub use stream::{OutputLine, StreamHandler, StreamResult, StreamableCommand};
472
473pub use command::{
474    attach::{AttachCommand, AttachResult},
475    bake::BakeCommand,
476    build::{BuildCommand, BuildOutput},
477    builder::{
478        BuilderBuildCommand, BuilderInfo, BuilderPruneCommand, BuilderPruneResult,
479        BuildxCreateCommand, BuildxCreateResult, BuildxInspectCommand, BuildxInspectResult,
480        BuildxLsCommand, BuildxLsResult, BuildxRmCommand, BuildxRmResult, BuildxStopCommand,
481        BuildxStopResult, BuildxUseCommand, BuildxUseResult,
482    },
483    commit::{CommitCommand, CommitResult},
484    container_prune::{ContainerPruneCommand, ContainerPruneResult},
485    context::{
486        ContextCreateCommand, ContextInfo, ContextInspectCommand, ContextLsCommand,
487        ContextRmCommand, ContextUpdateCommand, ContextUseCommand,
488    },
489    cp::{CpCommand, CpResult},
490    create::{CreateCommand, CreateResult},
491    diff::{DiffCommand, DiffResult, FilesystemChange, FilesystemChangeType},
492    events::{DockerEvent, EventActor, EventsCommand, EventsResult},
493    exec::{ExecCommand, ExecOutput},
494    export::{ExportCommand, ExportResult},
495    generic::GenericCommand,
496    history::{HistoryCommand, HistoryResult, ImageLayer},
497    image_prune::{DeletedImage, ImagePruneCommand, ImagePruneResult},
498    images::{ImageInfo, ImagesCommand, ImagesOutput},
499    import::{ImportCommand, ImportResult},
500    info::{DockerInfo as SystemDockerInfo, InfoCommand, InfoOutput, SystemInfo},
501    init::{InitCommand, InitOutput, InitTemplate},
502    inspect::{InspectCommand, InspectOutput},
503    kill::{KillCommand, KillResult},
504    load::{LoadCommand, LoadResult},
505    login::{LoginCommand, LoginOutput},
506    logout::{LogoutCommand, LogoutOutput},
507    logs::LogsCommand,
508    network::{
509        NetworkConnectCommand, NetworkConnectResult, NetworkCreateCommand, NetworkCreateResult,
510        NetworkDisconnectCommand, NetworkDisconnectResult, NetworkInfo, NetworkInspectCommand,
511        NetworkInspectOutput, NetworkLsCommand, NetworkLsOutput, NetworkPruneCommand,
512        NetworkPruneResult, NetworkRmCommand, NetworkRmResult,
513    },
514    pause::{PauseCommand, PauseResult},
515    port::{PortCommand, PortMapping as PortMappingInfo, PortResult},
516    ps::{ContainerInfo, PsCommand, PsFormat, PsOutput},
517    pull::PullCommand,
518    push::PushCommand,
519    rename::{RenameCommand, RenameResult},
520    restart::{RestartCommand, RestartResult},
521    rm::{RmCommand, RmResult},
522    rmi::{RmiCommand, RmiResult},
523    run::{ContainerId, MountType, RunCommand, VolumeMount},
524    save::{SaveCommand, SaveResult},
525    search::{RepositoryInfo, SearchCommand, SearchOutput},
526    start::{StartCommand, StartResult},
527    stats::{ContainerStats, StatsCommand, StatsResult},
528    stop::{StopCommand, StopResult},
529    system::{
530        BuildCacheInfo, BuildCacheUsage, ContainerInfo as SystemContainerInfo, ContainerUsage,
531        DiskUsage, ImageInfo as SystemImageInfo, ImageUsage, PruneResult, SystemDfCommand,
532        SystemPruneCommand, VolumeInfo as SystemVolumeInfo, VolumeUsage,
533    },
534    tag::{TagCommand, TagResult},
535    top::{ContainerProcess, TopCommand, TopResult},
536    unpause::{UnpauseCommand, UnpauseResult},
537    update::{UpdateCommand, UpdateResult},
538    version::{ClientVersion, ServerVersion, VersionCommand, VersionInfo, VersionOutput},
539    volume::{
540        VolumeCreateCommand, VolumeCreateResult, VolumeInfo, VolumeInspectCommand,
541        VolumeInspectOutput, VolumeLsCommand, VolumeLsOutput, VolumePruneCommand,
542        VolumePruneResult, VolumeRmCommand, VolumeRmResult,
543    },
544    wait::{WaitCommand, WaitResult},
545    CommandExecutor, CommandOutput, DockerCommand, EnvironmentBuilder, PortBuilder, PortMapping,
546    Protocol, DEFAULT_COMMAND_TIMEOUT,
547};
548pub use debug::{BackoffStrategy, DebugConfig, DebugExecutor, DryRunPreview, RetryPolicy};
549pub use error::{Error, Result};
550pub use platform::{Platform, PlatformInfo, Runtime};
551
552// Swarm commands (feature-gated)
553#[cfg(feature = "swarm")]
554pub use command::swarm::{
555    SwarmCaCommand, SwarmCaResult, SwarmInitCommand, SwarmInitResult, SwarmJoinCommand,
556    SwarmJoinResult, SwarmJoinTokenCommand, SwarmJoinTokenResult, SwarmLeaveCommand,
557    SwarmLeaveResult, SwarmNodeRole, SwarmUnlockCommand, SwarmUnlockKeyCommand,
558    SwarmUnlockKeyResult, SwarmUnlockResult, SwarmUpdateCommand, SwarmUpdateResult,
559};
560
561// Manifest commands (feature-gated)
562#[cfg(feature = "manifest")]
563pub use command::manifest::{
564    ManifestAnnotateCommand, ManifestAnnotateResult, ManifestCreateCommand, ManifestCreateResult,
565    ManifestInfo, ManifestInspectCommand, ManifestPlatform, ManifestPushCommand,
566    ManifestPushResult, ManifestRmCommand, ManifestRmResult,
567};
568
569pub use prerequisites::{
570    ensure_docker, ensure_docker_with_timeout, DockerInfo, DockerPrerequisites,
571    DEFAULT_PREREQ_TIMEOUT,
572};
573
574#[cfg(any(
575    feature = "templates",
576    feature = "template-redis",
577    feature = "template-redis-cluster",
578    feature = "template-redis-enterprise",
579    feature = "template-postgres",
580    feature = "template-mysql",
581    feature = "template-mongodb",
582    feature = "template-nginx"
583))]
584pub use template::{Template, TemplateBuilder, TemplateConfig, TemplateError};
585
586// Redis templates
587#[cfg(feature = "template-redis")]
588pub use template::redis::{RedisInsightTemplate, RedisTemplate};
589
590#[cfg(feature = "template-redis")]
591pub use template::redis::{RedisSentinelTemplate, SentinelConnectionInfo, SentinelInfo};
592
593#[cfg(feature = "template-redis-cluster")]
594pub use template::redis::{
595    ClusterInfo, NodeInfo, NodeRole, RedisClusterConnection, RedisClusterTemplate,
596};
597
598#[cfg(feature = "template-redis-enterprise")]
599pub use template::redis::{RedisEnterpriseConnectionInfo, RedisEnterpriseTemplate};
600
601// Database templates
602#[cfg(feature = "template-postgres")]
603pub use template::database::{PostgresConnectionString, PostgresTemplate};
604
605#[cfg(feature = "template-mysql")]
606pub use template::database::{MysqlConnectionString, MysqlTemplate};
607
608#[cfg(feature = "template-mongodb")]
609pub use template::database::{MongodbConnectionString, MongodbTemplate};
610
611// Web server templates
612#[cfg(feature = "template-nginx")]
613pub use template::web::NginxTemplate;
614
615/// The version of this crate
616pub const VERSION: &str = env!("CARGO_PKG_VERSION");
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621
622    #[test]
623    fn test_version() {
624        // Verify version follows semver format (major.minor.patch)
625        let parts: Vec<&str> = VERSION.split('.').collect();
626        assert!(parts.len() >= 3, "Version should have at least 3 parts");
627
628        // Verify each part is numeric
629        for part in &parts[0..3] {
630            assert!(
631                part.chars().all(|c| c.is_ascii_digit()),
632                "Version parts should be numeric"
633            );
634        }
635    }
636}