provisioning-platform/prov-ecosystem/crates/runtime/INTEGRATION.md

18 KiB

Runtime Crate - Comprehensive Integration Guide

Overview

The Runtime crate provides a unified abstraction layer for managing multiple container runtimes across different platforms and architectures. This guide covers advanced integration patterns, platform-specific considerations, and best practices for using the runtime crate in production environments.

Table of Contents

  1. Supported Runtimes
  2. Platform Detection Strategy
  3. Socket Path Configuration
  4. Remote Runtime Access
  5. Docker Compose Integration
  6. Configuration Patterns
  7. Error Handling
  8. Performance Tuning
  9. Security Considerations
  10. Troubleshooting

Supported Runtimes

Docker

Platforms: Linux, macOS (Docker Desktop), Windows (WSL2)

Socket Paths:

  • Linux: /var/run/docker.sock
  • macOS: ~/.docker/run/docker.sock (Docker Desktop)
  • Windows/WSL2: /var/run/docker.sock

Key Features:

  • Industry-standard container runtime
  • Full Docker Compose support
  • TLS certificate support
  • Buildkit support

Configuration Example:

use runtime::{RuntimeConfig, RuntimeType};

let config = RuntimeConfig {
    runtime: RuntimeType::Docker,
    socket_path: "/var/run/docker.sock".to_string(),
    environment: [
        ("DOCKER_HOST".to_string(), "unix:///var/run/docker.sock".to_string()),
    ].iter().cloned().collect(),
    ..Default::default()
};

Podman

Platforms: Linux (all distributions), macOS (rootless mode)

Socket Paths:

  • Linux root: /run/podman/podman.sock
  • Linux rootless: $XDG_RUNTIME_DIR/podman/podman.sock
  • macOS: $XDG_RUNTIME_DIR/podman/podman.sock

Key Features:

  • Docker-compatible API
  • Rootless container execution
  • Systemd integration
  • Podman Compose support

Rootless Configuration:

use runtime::{RuntimeConfig, RuntimeType};

let xdg_runtime = std::env::var("XDG_RUNTIME_DIR")
    .unwrap_or_else(|_| format!("/run/user/{}", unsafe { libc::getuid() }));

let config = RuntimeConfig {
    runtime: RuntimeType::Podman,
    socket_path: format!("{}/podman/podman.sock", xdg_runtime),
    ..Default::default()
};

OrbStack

Platforms: macOS (Intel and Apple Silicon) - Default macOS runtime

Socket Path: ~/.orbstack/run/docker.sock

Key Features:

  • Fastest macOS container runtime
  • Native integration with macOS filesystem
  • Full Docker Compose compatibility
  • Minimal resource overhead

Configuration Example:

use runtime::{RuntimeConfig, RuntimeType};

let home = std::env::var("HOME").expect("HOME not set");
let config = RuntimeConfig {
    runtime: RuntimeType::OrbStack,
    socket_path: format!("{}/.orbstack/run/docker.sock", home),
    ..Default::default()
};

Colima

Platforms: macOS, Linux (via Lima)

Socket Paths:

  • macOS: ~/.colima/docker.sock
  • Linux: /var/run/docker.sock (proxied via Colima)

Key Features:

  • Lightweight VM-based runtime
  • Docker CLI compatibility
  • Resource isolation
  • Cross-platform support

nerdctl

Platforms: Linux, macOS, Windows

Socket Path: unix:///run/containerd/containerd.sock

Key Features:

  • containerd-based runtime
  • Docker-compatible CLI
  • Efficient resource usage
  • Kubernetes integration

Platform Detection Strategy

The runtime crate implements intelligent platform-specific detection:

Linux Detection Priority

1. Docker (socket: /var/run/docker.sock)
2. Podman rootless (socket: $XDG_RUNTIME_DIR/podman/podman.sock)
3. Podman root (socket: /run/podman/podman.sock)
4. nerdctl (socket: unix:///run/containerd/containerd.sock)
5. Colima (socket: ~/.colima/docker.sock)

Implementation:

use runtime::detect_runtime;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let runtime = detect_runtime().await?;

    match runtime {
        RuntimeType::Docker => println!("Using Docker"),
        RuntimeType::Podman => println!("Using Podman"),
        _ => println!("Using: {}", runtime.name()),
    }

    Ok(())
}

macOS Detection Priority

1. OrbStack (socket: ~/.orbstack/run/docker.sock)
2. Docker Desktop (socket: ~/.docker/run/docker.sock)
3. Colima (socket: ~/.colima/docker.sock)
4. Podman (socket: $XDG_RUNTIME_DIR/podman/podman.sock)
5. nerdctl (socket: unix:///run/containerd/containerd.sock)

Rationale: OrbStack offers superior performance on macOS compared to Docker Desktop, so it's preferred when available.

Windows Detection

Windows requires WSL2 (Windows Subsystem for Linux 2) for container support. The detection mechanism checks for available runtimes within WSL2 and follows the Linux priority list.

Socket Path Configuration

Standard Socket Paths

Each runtime exposes a well-known socket path for communication:

use runtime::RuntimeType;

fn get_socket_path(runtime: RuntimeType) -> String {
    match runtime {
        RuntimeType::Docker => "/var/run/docker.sock".to_string(),
        RuntimeType::Podman => {
            std::env::var("XDG_RUNTIME_DIR")
                .map(|xdg| format!("{}/podman/podman.sock", xdg))
                .unwrap_or_else(|_| "/run/podman/podman.sock".to_string())
        }
        RuntimeType::OrbStack => {
            let home = std::env::var("HOME").expect("HOME not set");
            format!("{}/.orbstack/run/docker.sock", home)
        }
        RuntimeType::Colima => {
            let home = std::env::var("HOME").expect("HOME not set");
            format!("{}/.colima/docker.sock", home)
        }
        RuntimeType::Nerdctl => "unix:///run/containerd/containerd.sock".to_string(),
    }
}

Custom Socket Paths

For non-standard configurations (e.g., custom Docker socket location, remote access):

use runtime::{RuntimeConfig, RuntimeType};

let config = RuntimeConfig {
    runtime: RuntimeType::Docker,
    socket_path: "/custom/docker.sock".to_string(),
    ..Default::default()
};

Environment Variable Overrides

Use environment variables for dynamic configuration:

use runtime::RuntimeConfig;

let socket_path = std::env::var("RUNTIME_SOCKET_PATH")
    .unwrap_or_else(|_| "/var/run/docker.sock".to_string());

let config = RuntimeConfig {
    socket_path,
    ..Default::default()
};

Remote Runtime Access

SSH-Based Remote Access

Configure remote runtime access via SSH:

use runtime::{RuntimeConfig, RemoteConfig};

let remote = RemoteConfig {
    enabled: true,
    host: "docker.example.com".to_string(),
    user: "docker-user".to_string(),
    port: 22,
    private_key: Some("/home/user/.ssh/id_rsa".to_string()),
    password: None,
    known_hosts: Some("/home/user/.ssh/known_hosts".to_string()),
    jump_host: None, // For bastion/jump host scenarios
};

let config = RuntimeConfig {
    remote_config: Some(remote),
    ..Default::default()
};

Jump Host Configuration

For accessing runtimes through a bastion host:

use runtime::{RuntimeConfig, RemoteConfig};

let jump_host = RemoteConfig {
    host: "bastion.example.com".to_string(),
    user: "bastion-user".to_string(),
    private_key: Some("/home/user/.ssh/bastion_key".to_string()),
    ..Default::default()
};

let remote = RemoteConfig {
    enabled: true,
    host: "docker.internal.example.com".to_string(),
    user: "docker-user".to_string(),
    private_key: Some("/home/user/.ssh/docker_key".to_string()),
    jump_host: Some(Box::new(jump_host)),
    ..Default::default()
};

let config = RuntimeConfig {
    remote_config: Some(remote),
    ..Default::default()
};

TLS/HTTPS Remote Access

use runtime::RuntimeConfig;

let config = RuntimeConfig {
    socket_path: "tcp://docker.example.com:2376".to_string(),
    environment: [
        ("DOCKER_HOST".to_string(), "tcp://docker.example.com:2376".to_string()),
        ("DOCKER_CERT_PATH".to_string(), "/path/to/certs".to_string()),
        ("DOCKER_TLS_VERIFY".to_string(), "1".to_string()),
    ].iter().cloned().collect(),
    ..Default::default()
};

Docker Compose Integration

Automatic Compose Adaptation

The runtime crate can adapt Docker Compose files for different runtimes:

use runtime::adapt_compose;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let adapted = adapt_compose(
        "docker-compose.yaml",
        RuntimeType::Podman
    ).await?;

    println!("Adapted compose: {}", adapted);
    Ok(())
}

Compose File Differences

Different runtimes may require compose file adjustments:

Docker (Standard):

version: '3.8'
services:
  app:
    image: my-app:latest
    ports:
      - "8080:8080"

Podman (Rootless considerations):

version: '3.8'
services:
  app:
    image: my-app:latest
    ports:
      - "8080:8080"
    # May require podman-compose specific directives

OrbStack (Filesystem integration):

version: '3.8'
services:
  app:
    image: my-app:latest
    volumes:
      - ./data:/data:z  # z flag for SELinux compatibility

Compose Version Support

  • Version 3.x: Fully supported across all runtimes
  • Version 2.x: Limited support; migration recommended
  • Buildx Compose: Docker and Podman only

Configuration Patterns

Environment-Based Configuration

use runtime::{RuntimeConfig, RuntimeType};

fn load_config_from_env() -> RuntimeConfig {
    let runtime_type = std::env::var("CONTAINER_RUNTIME")
        .unwrap_or_else(|_| "docker".to_string());

    let socket_path = std::env::var("CONTAINER_SOCKET")
        .unwrap_or_else(|_| {
            match runtime_type.as_str() {
                "podman" => "$XDG_RUNTIME_DIR/podman/podman.sock".to_string(),
                _ => "/var/run/docker.sock".to_string(),
            }
        });

    RuntimeConfig {
        runtime: RuntimeType::from_str(&runtime_type).unwrap_or(RuntimeType::Docker),
        socket_path,
        ..Default::default()
    }
}

File-Based Configuration

use serde::{Deserialize, Serialize};
use std::fs;

#[derive(Serialize, Deserialize)]
struct RuntimeSettings {
    runtime: String,
    socket_path: String,
    environment: std::collections::HashMap<String, String>,
}

fn load_config_from_file(path: &str) -> Result<RuntimeSettings, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(path)?;
    let settings: RuntimeSettings = serde_json::from_str(&content)?;
    Ok(settings)
}

Builder Pattern Configuration

use runtime::{RuntimeConfig, RuntimeType};

let config = RuntimeConfig::builder()
    .runtime(RuntimeType::Docker)
    .socket_path("/var/run/docker.sock")
    .with_env("DOCKER_BUILDKIT", "1")
    .with_env("COMPOSE_DOCKER_CLI_BUILD", "1")
    .build();

Error Handling

Common Error Scenarios

Socket Not Found:

use runtime::{detect_runtime, RuntimeError};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    match detect_runtime().await {
        Ok(runtime) => println!("Found: {}", runtime.name()),
        Err(RuntimeError::SocketNotFound(path)) => {
            eprintln!("Docker socket not found at {}", path);
            eprintln!("Please ensure Docker/Podman is running");
        }
        Err(e) => eprintln!("Error: {}", e),
    }
    Ok(())
}

Permission Denied:

use runtime::RuntimeError;

match some_operation().await {
    Err(RuntimeError::PermissionDenied) => {
        eprintln!("Cannot access runtime socket");
        eprintln!("Try: sudo usermod -aG docker $USER");
    }
    Err(e) => eprintln!("Error: {}", e),
    Ok(result) => println!("Success: {:?}", result),
}

Connection Timeout:

use runtime::RuntimeError;
use std::time::Duration;

match runtime.with_timeout(Duration::from_secs(5)).info().await {
    Err(RuntimeError::Timeout) => {
        eprintln!("Runtime unresponsive (timeout after 5s)");
        eprintln!("Check if Docker/Podman is running");
    }
    Err(e) => eprintln!("Error: {}", e),
    Ok(info) => println!("Runtime info: {:?}", info),
}

Performance Tuning

Connection Pooling

For applications making many runtime calls:

use runtime::{RuntimeConfig, RuntimePool};

let pool = RuntimePool::with_size(
    RuntimeConfig::default(),
    10  // Pool size
);

// Acquire connections from pool
let runtime = pool.get().await?;
runtime.info().await?;
runtime.close().await?;

Caching Runtime Information

use runtime::detect_runtime;
use std::sync::Arc;
use tokio::sync::Mutex;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let runtime = Arc::new(Mutex::new(detect_runtime().await?));

    // Use cached runtime across async tasks
    let runtime_clone = Arc::clone(&runtime);
    tokio::spawn(async move {
        let rt = runtime_clone.lock().await;
        // Use cached runtime
    }).await?;

    Ok(())
}

Batch Operations

use runtime::RuntimeBatch;

let batch = RuntimeBatch::new(runtime);

batch
    .add_pull("image:tag")
    .add_build("/path/to/dockerfile", "image:tag")
    .add_run("image:tag", vec!["command"])
    .execute()
    .await?;

Security Considerations

Socket Permissions

Ensure proper socket file permissions:

# Docker socket (default)
ls -l /var/run/docker.sock
# Should be: srw-rw---- root docker

# Podman socket (rootless)
ls -l $XDG_RUNTIME_DIR/podman/podman.sock
# Should be: srw------- (user only)

User Privileges

For rootless containers:

use runtime::RuntimeConfig;

// Verify running as unprivileged user
if unsafe { libc::getuid() } == 0 {
    eprintln!("Warning: Running container operations as root");
    eprintln!("Consider using rootless podman for security");
}

let config = RuntimeConfig {
    // Configure for rootless operation
    ..Default::default()
};

TLS Certificates

For remote access with TLS:

use runtime::RuntimeConfig;

let config = RuntimeConfig {
    tls_config: Some(TlsConfig {
        ca_cert: "/path/to/ca.pem".to_string(),
        client_cert: "/path/to/cert.pem".to_string(),
        client_key: "/path/to/key.pem".to_string(),
        verify: true,
    }),
    ..Default::default()
};

Environment Variable Sanitization

use runtime::RuntimeConfig;

// Avoid storing sensitive data in environment variables
let config = RuntimeConfig {
    environment: [
        // Only non-sensitive variables
        ("DOCKER_BUILDKIT".to_string(), "1".to_string()),
    ].iter().cloned().collect(),
    // Load secrets from secure sources instead
    ..Default::default()
};

Troubleshooting

Runtime Not Detected

Symptoms: detect_runtime() fails or returns unexpected runtime

Diagnosis:

# Check if socket exists
ls -l /var/run/docker.sock
# or
echo $XDG_RUNTIME_DIR
ls -l $XDG_RUNTIME_DIR/podman/podman.sock

Solutions:

  1. Ensure container runtime is installed
  2. Start the runtime service: systemctl start docker or systemctl --user start podman
  3. Check socket permissions: sudo usermod -aG docker $USER

Connection Refused

Symptoms: Operations fail with "Connection refused"

Diagnosis:

# Test socket connectivity
docker ps  # or podman ps
# If this fails, daemon isn't running

Solutions:

  1. Start Docker/Podman daemon
  2. Check if socket is accessible
  3. Verify firewall rules for remote access

Compose Adaptation Issues

Symptoms: Adapted compose file has errors

Solutions:

  1. Validate compose file: docker compose config
  2. Check runtime-specific requirements
  3. Review crate documentation for compatibility matrix

Performance Issues

Symptoms: Slow image pulls, container operations

Solutions:

  1. Check network connectivity
  2. Monitor disk space for image storage
  3. Enable BuildKit: export DOCKER_BUILDKIT=1
  4. Consider using image registry caching

Integration Patterns

Kubernetes Integration

For Kubernetes environments using Docker or CRI-compatible runtimes:

use runtime::detect_runtime;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Detect runtime as per container spec
    let runtime = detect_runtime().await?;

    // Use for image operations before pod deployment
    runtime.pull("image:tag").await?;

    Ok(())
}

CI/CD Pipeline Integration

use runtime::{detect_runtime, RuntimeConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Auto-detect available runtime in CI environment
    let runtime = detect_runtime().await?;

    // Build image
    runtime.build("/app", "my-image:latest").await?;

    // Test container
    runtime.run("my-image:latest", vec!["cargo", "test"]).await?;

    // Push to registry
    runtime.push("my-image:latest").await?;

    Ok(())
}

Multi-Environment Deployment

use runtime::{RuntimeConfig, RuntimeType};

fn get_config_for_environment(env: &str) -> RuntimeConfig {
    match env {
        "development" => {
            // Local development: use detected runtime
            RuntimeConfig::default()
        }
        "staging" => {
            // Staging: connect to staging Docker host
            RuntimeConfig {
                socket_path: "tcp://docker.staging.internal:2376".to_string(),
                ..Default::default()
            }
        }
        "production" => {
            // Production: use production Docker host
            RuntimeConfig {
                socket_path: "tcp://docker.prod.internal:2376".to_string(),
                ..Default::default()
            }
        }
        _ => RuntimeConfig::default(),
    }
}

Conclusion

The Runtime crate provides flexible, production-ready container runtime abstraction. Proper configuration, error handling, and security practices ensure reliable container operations across diverse environments.

For more information, see: