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.8", 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.8", 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.8", features = ["swarm"] }
242//! ```
243//!
244//! ## `manifest` - Multi-arch Manifest Commands
245//!
246//! ```toml
247//! docker-wrapper = { version = "0.8", features = ["manifest"] }
248//! ```
249//!
250//! # Streaming Output
251//!
252//! For long-running commands, stream output in real-time:
253//!
254//! ```rust,no_run
255//! use docker_wrapper::{BuildCommand, StreamHandler, StreamableCommand};
256//!
257//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
258//! let result = BuildCommand::new(".")
259//! .tag("my-app:latest")
260//! .stream(StreamHandler::print())
261//! .await?;
262//!
263//! if result.is_success() {
264//! println!("Build complete!");
265//! }
266//! # Ok(())
267//! # }
268//! ```
269//!
270//! # Checking Docker Availability
271//!
272//! ```rust,no_run
273//! use docker_wrapper::ensure_docker;
274//!
275//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
276//! let info = ensure_docker().await?;
277//! println!("Docker {}.{}.{}", info.version.major, info.version.minor, info.version.patch);
278//! # Ok(())
279//! # }
280//! ```
281//!
282//! # Why docker-wrapper?
283//!
284//! This crate wraps the Docker CLI rather than calling the Docker API directly
285//! (like [bollard](https://crates.io/crates/bollard)).
286//!
287//! **docker-wrapper advantages:**
288//! - Just needs `docker` in PATH (no socket access required)
289//! - Native `docker compose` support
290//! - Works with Docker, Podman, Colima, and other Docker-compatible CLIs
291//! - Familiar mental model if you know Docker CLI
292//!
293//! **bollard advantages:**
294//! - Direct API calls (no process spawn overhead)
295//! - Lower latency for high-frequency operations
296//! - No external binary dependency
297//!
298//! **Choose docker-wrapper** for CLI tools, dev tooling, Compose workflows, or
299//! when working with Docker alternatives.
300//!
301//! **Choose bollard** for high-performance services with many Docker operations.
302
303#![warn(missing_docs)]
304#![warn(clippy::all)]
305#![warn(clippy::pedantic)]
306
307pub mod command;
308#[cfg(feature = "compose")]
309pub mod compose;
310pub mod debug;
311pub mod error;
312pub mod platform;
313pub mod prerequisites;
314pub mod stream;
315#[cfg(any(
316 feature = "templates",
317 feature = "template-redis",
318 feature = "template-redis-cluster",
319 feature = "template-postgres",
320 feature = "template-mysql",
321 feature = "template-mongodb",
322 feature = "template-nginx"
323))]
324/// Container templates module
325///
326/// Provides pre-configured container templates with sensible defaults for common services.
327/// Templates support custom images, platforms, persistence, and resource configuration.
328///
329/// See the [Template Guide](https://github.com/joshrotenberg/docker-wrapper/blob/main/docs/TEMPLATES.md) for comprehensive documentation.
330///
331/// # Available Templates
332///
333/// ## Redis Templates
334/// - [`RedisTemplate`] - Basic Redis server
335/// - [`RedisSentinelTemplate`] - High-availability Redis with Sentinel
336/// - [`RedisClusterTemplate`] - Sharded Redis cluster
337/// - [`RedisEnterpriseTemplate`] - Redis Enterprise with management
338/// - [`RedisInsightTemplate`] - Redis management UI
339///
340/// ## Database Templates
341/// - [`PostgresTemplate`] - PostgreSQL database
342/// - [`MysqlTemplate`] - MySQL database
343/// - [`MongodbTemplate`] - MongoDB document database
344///
345/// ## Web Server Templates
346/// - [`NginxTemplate`] - Nginx web server
347///
348/// # Quick Start
349///
350/// ```rust,no_run
351/// use docker_wrapper::{RedisTemplate, Template};
352///
353/// # #[tokio::main]
354/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
355/// let redis = RedisTemplate::new("my-redis")
356/// .port(6379)
357/// .password("secret")
358/// .with_persistence("redis-data");
359///
360/// let container_id = redis.start().await?;
361/// println!("Redis started: {}", container_id);
362/// # Ok(())
363/// # }
364/// ```
365#[cfg(any(
366 feature = "templates",
367 feature = "template-redis",
368 feature = "template-redis-cluster",
369 feature = "template-postgres",
370 feature = "template-mysql",
371 feature = "template-mongodb",
372 feature = "template-nginx"
373))]
374pub mod template;
375
376pub use stream::{OutputLine, StreamHandler, StreamResult, StreamableCommand};
377
378pub use command::{
379 attach::{AttachCommand, AttachResult},
380 bake::BakeCommand,
381 build::{BuildCommand, BuildOutput},
382 builder::{
383 BuilderBuildCommand, BuilderInfo, BuilderPruneCommand, BuilderPruneResult,
384 BuildxCreateCommand, BuildxCreateResult, BuildxInspectCommand, BuildxInspectResult,
385 BuildxLsCommand, BuildxLsResult, BuildxRmCommand, BuildxRmResult, BuildxStopCommand,
386 BuildxStopResult, BuildxUseCommand, BuildxUseResult,
387 },
388 commit::{CommitCommand, CommitResult},
389 container_prune::{ContainerPruneCommand, ContainerPruneResult},
390 context::{
391 ContextCreateCommand, ContextInfo, ContextInspectCommand, ContextLsCommand,
392 ContextRmCommand, ContextUpdateCommand, ContextUseCommand,
393 },
394 cp::{CpCommand, CpResult},
395 create::{CreateCommand, CreateResult},
396 diff::{DiffCommand, DiffResult, FilesystemChange, FilesystemChangeType},
397 events::{DockerEvent, EventActor, EventsCommand, EventsResult},
398 exec::{ExecCommand, ExecOutput},
399 export::{ExportCommand, ExportResult},
400 generic::GenericCommand,
401 history::{HistoryCommand, HistoryResult, ImageLayer},
402 image_prune::{DeletedImage, ImagePruneCommand, ImagePruneResult},
403 images::{ImageInfo, ImagesCommand, ImagesOutput},
404 import::{ImportCommand, ImportResult},
405 info::{DockerInfo as SystemDockerInfo, InfoCommand, InfoOutput, SystemInfo},
406 init::{InitCommand, InitOutput, InitTemplate},
407 inspect::{InspectCommand, InspectOutput},
408 kill::{KillCommand, KillResult},
409 load::{LoadCommand, LoadResult},
410 login::{LoginCommand, LoginOutput},
411 logout::{LogoutCommand, LogoutOutput},
412 logs::LogsCommand,
413 network::{
414 NetworkConnectCommand, NetworkConnectResult, NetworkCreateCommand, NetworkCreateResult,
415 NetworkDisconnectCommand, NetworkDisconnectResult, NetworkInfo, NetworkInspectCommand,
416 NetworkInspectOutput, NetworkLsCommand, NetworkLsOutput, NetworkPruneCommand,
417 NetworkPruneResult, NetworkRmCommand, NetworkRmResult,
418 },
419 pause::{PauseCommand, PauseResult},
420 port::{PortCommand, PortMapping as PortMappingInfo, PortResult},
421 ps::{ContainerInfo, PsCommand, PsFormat, PsOutput},
422 pull::PullCommand,
423 push::PushCommand,
424 rename::{RenameCommand, RenameResult},
425 restart::{RestartCommand, RestartResult},
426 rm::{RmCommand, RmResult},
427 rmi::{RmiCommand, RmiResult},
428 run::{ContainerId, MountType, RunCommand, VolumeMount},
429 save::{SaveCommand, SaveResult},
430 search::{RepositoryInfo, SearchCommand, SearchOutput},
431 start::{StartCommand, StartResult},
432 stats::{ContainerStats, StatsCommand, StatsResult},
433 stop::{StopCommand, StopResult},
434 system::{
435 BuildCacheInfo, BuildCacheUsage, ContainerInfo as SystemContainerInfo, ContainerUsage,
436 DiskUsage, ImageInfo as SystemImageInfo, ImageUsage, PruneResult, SystemDfCommand,
437 SystemPruneCommand, VolumeInfo as SystemVolumeInfo, VolumeUsage,
438 },
439 tag::{TagCommand, TagResult},
440 top::{ContainerProcess, TopCommand, TopResult},
441 unpause::{UnpauseCommand, UnpauseResult},
442 update::{UpdateCommand, UpdateResult},
443 version::{ClientVersion, ServerVersion, VersionCommand, VersionInfo, VersionOutput},
444 volume::{
445 VolumeCreateCommand, VolumeCreateResult, VolumeInfo, VolumeInspectCommand,
446 VolumeInspectOutput, VolumeLsCommand, VolumeLsOutput, VolumePruneCommand,
447 VolumePruneResult, VolumeRmCommand, VolumeRmResult,
448 },
449 wait::{WaitCommand, WaitResult},
450 CommandExecutor, CommandOutput, DockerCommand, EnvironmentBuilder, PortBuilder, PortMapping,
451 Protocol, DEFAULT_COMMAND_TIMEOUT,
452};
453pub use debug::{BackoffStrategy, DebugConfig, DebugExecutor, DryRunPreview, RetryPolicy};
454pub use error::{Error, Result};
455pub use platform::{Platform, PlatformInfo, Runtime};
456
457// Swarm commands (feature-gated)
458#[cfg(feature = "swarm")]
459pub use command::swarm::{
460 SwarmCaCommand, SwarmCaResult, SwarmInitCommand, SwarmInitResult, SwarmJoinCommand,
461 SwarmJoinResult, SwarmJoinTokenCommand, SwarmJoinTokenResult, SwarmLeaveCommand,
462 SwarmLeaveResult, SwarmNodeRole, SwarmUnlockCommand, SwarmUnlockKeyCommand,
463 SwarmUnlockKeyResult, SwarmUnlockResult, SwarmUpdateCommand, SwarmUpdateResult,
464};
465
466// Manifest commands (feature-gated)
467#[cfg(feature = "manifest")]
468pub use command::manifest::{
469 ManifestAnnotateCommand, ManifestAnnotateResult, ManifestCreateCommand, ManifestCreateResult,
470 ManifestInfo, ManifestInspectCommand, ManifestPlatform, ManifestPushCommand,
471 ManifestPushResult, ManifestRmCommand, ManifestRmResult,
472};
473
474pub use prerequisites::{
475 ensure_docker, ensure_docker_with_timeout, DockerInfo, DockerPrerequisites,
476 DEFAULT_PREREQ_TIMEOUT,
477};
478
479#[cfg(any(
480 feature = "templates",
481 feature = "template-redis",
482 feature = "template-redis-cluster",
483 feature = "template-postgres",
484 feature = "template-mysql",
485 feature = "template-mongodb",
486 feature = "template-nginx"
487))]
488pub use template::{Template, TemplateBuilder, TemplateConfig, TemplateError};
489
490// Redis templates
491#[cfg(feature = "template-redis")]
492pub use template::redis::{RedisInsightTemplate, RedisTemplate};
493
494#[cfg(feature = "template-redis")]
495pub use template::redis::{RedisSentinelTemplate, SentinelConnectionInfo, SentinelInfo};
496
497#[cfg(feature = "template-redis-cluster")]
498pub use template::redis::{
499 ClusterInfo, NodeInfo, NodeRole, RedisClusterConnection, RedisClusterTemplate,
500};
501
502#[cfg(feature = "template-redis-enterprise")]
503pub use template::redis::{RedisEnterpriseConnectionInfo, RedisEnterpriseTemplate};
504
505// Database templates
506#[cfg(feature = "template-postgres")]
507pub use template::database::{PostgresConnectionString, PostgresTemplate};
508
509#[cfg(feature = "template-mysql")]
510pub use template::database::{MysqlConnectionString, MysqlTemplate};
511
512#[cfg(feature = "template-mongodb")]
513pub use template::database::{MongodbConnectionString, MongodbTemplate};
514
515// Web server templates
516#[cfg(feature = "template-nginx")]
517pub use template::web::NginxTemplate;
518
519/// The version of this crate
520pub const VERSION: &str = env!("CARGO_PKG_VERSION");
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525
526 #[test]
527 fn test_version() {
528 // Verify version follows semver format (major.minor.patch)
529 let parts: Vec<&str> = VERSION.split('.').collect();
530 assert!(parts.len() >= 3, "Version should have at least 3 parts");
531
532 // Verify each part is numeric
533 for part in &parts[0..3] {
534 assert!(
535 part.chars().all(|c| c.is_ascii_digit()),
536 "Version parts should be numeric"
537 );
538 }
539 }
540}