Outcall
SpecificationsS008 · Docker Manager

S008 · Docker Manager

Specification module 008-docker-manager

S008: Docker Manager

FieldValue
SpecS008
FeatureDocker Manager
Date2026-04-21
StatusDraft
Author@marktopper

Overview

The Docker Manager handles the full lifecycle of agent containers within outcalld. It uses the bollard crate to communicate with the Docker Engine API — creating, starting, monitoring, stopping, and removing containers with the correct security constraints enforced at creation time.

Every agent container is created with a deterministic set of constraints:

  • Attached to an outcall-managed network (S002)
  • agent.sock bind-mounted into the container (S004)
  • outcall-agent shim bind-mounted at /usr/local/bin/outcall (S005)
  • HTTP_PROXY / HTTPS_PROXY env vars pointed at the HTTP proxy (S006)
  • DNS resolver pointed at the DNS filter (S007)
  • The host socket (host.sock) is never mounted into containers

The host socket protection is the critical security invariant. Bollard enforces this at creation time by validating all bind mounts against a deny list before issuing the Docker API call. If any mount resolves to the host socket path, the create call is rejected before reaching Docker.

Container naming

All agent containers use the outcall-agent- prefix followed by a short random suffix (8 hex characters). The prefix is used for discovery and cleanup.

Lifecycle

create → start → monitor → stop → remove

The Docker Manager runs as a Tokio task inside outcalld. There is no separate process. The host operator manages containers through the outcall CLI and the outcalld host API.

User Scenarios

S008-US-001 [P1] As a host operator, I want to create agent containers with security constraints enforced so that agents cannot access the host surface.

S008-US-002 [P1] As a host operator, I want to list running agent containers so that I can see what is active.

S008-US-003 [P1] As a host operator, I want to stop and remove agent containers so that I can clean up resources.

S008-US-004 [P1] As a host operator, I want the host socket to never be mounted into containers so that agents cannot escalate to the host API.

S008-US-005 [P2] As a host operator, I want to inspect a specific container so that I can debug issues.

S008-US-006 [P1] As a host operator, I want containers to survive daemon restarts so that running workloads are not disrupted.

S008-US-007 [P2] As a host operator, I want to pull images before creating containers so that the create step does not block on network I/O.

Requirements Summary

IDTypePriorityTitleStatus
S008-FR-001FunctionalP1Docker Engine API onlyDraft
S008-FR-002FunctionalP1Container creation parametersDraft
S008-FR-003FunctionalP1Network attachmentDraft
S008-FR-004FunctionalP1Agent socket bind mountDraft
S008-FR-005FunctionalP1Shim binary bind mountDraft
S008-FR-006FunctionalP1Proxy env var injectionDraft
S008-FR-007FunctionalP1DNS resolver configurationDraft
S008-FR-008FunctionalP1Host socket deny listDraft
S008-FR-009FunctionalP1Bind mount validationDraft
S008-FR-010FunctionalP1Container naming conventionDraft
S008-FR-011FunctionalP1Container startDraft
S008-FR-012FunctionalP1Container stopDraft
S008-FR-013FunctionalP1Container removeDraft
S008-FR-014FunctionalP1Container listingDraft
S008-FR-015FunctionalP2Container inspectionDraft
S008-FR-016FunctionalP2Resource limitsDraft
S008-FR-017FunctionalP2Image pullingDraft
S008-FR-018FunctionalP1In-process Tokio taskDraft
S008-FR-019FunctionalP1Host-only API endpointsDraft
S008-FR-020FunctionalP1Containers outlive daemonDraft
S008-FR-021FunctionalP1CLI subcommandsDraft
S008-FR-022FunctionalP1CLI transport mechanismDraft
S008-FR-023FunctionalP1CLI error handlingDraft
S008-FR-024FunctionalP1Constants in outcall-apiDraft
S008-FR-025FunctionalP2Read-only root filesystemDraft
S008-FR-026FunctionalP2No privileged modeDraft
S008-FR-027FunctionalP2Capability droppingDraft
S008-FR-028FunctionalP1Stop timeoutDraft
S008-FR-029FunctionalP2Container labelsDraft
S008-FR-030FunctionalP1Network-up prerequisiteDraft
S008-FR-031FunctionalP1Container event streamDraft
S008-FR-032FunctionalP1Outcall-managed network definitionDraft
S008-AS-001AcceptanceP1Create agent containerDraft
S008-AS-002AcceptanceP1Host socket rejectedDraft
S008-AS-003AcceptanceP1List running containersDraft
S008-AS-004AcceptanceP1Stop containerDraft
S008-AS-005AcceptanceP1Remove containerDraft
S008-AS-006AcceptanceP1Network not readyDraft
S008-AS-007AcceptanceP2Inspect containerDraft
S008-AS-008AcceptanceP2Pull imageDraft
S008-AS-009AcceptanceP1Daemon not runningDraft
S008-AS-010AcceptanceP1Docker not availableDraft
S008-AS-011AcceptanceP1Containers survive shutdownDraft
S008-AS-012AcceptanceP1Container with resource limitsDraft
S008-AS-013AcceptanceP1Verify bind mounts inside containerDraft
S008-AS-014AcceptanceP1Verify env vars inside containerDraft
S008-IF-001InterfaceP1POST /api/v1/container/createDraft
S008-IF-002InterfaceP1POST /api/v1/container/stopDraft
S008-IF-003InterfaceP1POST /api/v1/container/removeDraft
S008-IF-004InterfaceP1GET /api/v1/containersDraft
S008-IF-005InterfaceP2GET /api/v1/containerDraft
S008-IF-006InterfaceP2POST /api/v1/container/pullDraft
S008-IF-007InterfaceP1CLI commandsDraft
S008-IF-008InterfaceP1CLI output formatDraft
S008-EC-001Edge CaseP1Host socket in bind mountsDraft
S008-EC-002Edge CaseP1Network does not existDraft
S008-EC-003Edge CaseP1Docker unreachableDraft
S008-EC-004Edge CaseP1Container does not exist (stop/remove)Draft
S008-EC-005Edge CaseP2Image not found locallyDraft
S008-EC-006Edge CaseP2Image pull failsDraft
S008-EC-007Edge CaseP1Container already stoppedDraft
S008-EC-008Edge CaseP2Container already removedDraft
S008-EC-009Edge CaseP2Rapid create callsDraft
S008-EC-010Edge CaseP2Daemon shutdown with running containersDraft
S008-EC-011Edge CaseP2Docker disappears at runtimeDraft
S008-EC-012Edge CaseP1Symlink traversal in bind mountsDraft
S008-EC-013Edge CaseP1Duplicate container nameDraft
S008-EC-014Edge CaseP2Resource limit exceeds host capacityDraft
S008-EC-015Edge CaseP1Agent socket path missingDraft
S008-EC-016Edge CaseP1Shim binary path missingDraft
S008-SC-001SuccessP1Container creation with all constraintsDraft
S008-SC-002SuccessP1Host socket never mountedDraft
S008-SC-003SuccessP1Network attachment verifiedDraft
S008-SC-004SuccessP1Bind mounts verified inside containerDraft
S008-SC-005SuccessP1Env vars verified inside containerDraft
S008-SC-006SuccessP1DNS resolver verifiedDraft
S008-SC-007SuccessP1Stop and remove lifecycleDraft
S008-SC-008SuccessP1Container listing accuracyDraft
S008-SC-009SuccessP1No docker CLI shellingDraft
S008-SC-010SuccessP2Graceful shutdown cleanupDraft
S008-SC-011SuccessP1CLI round-tripDraft

Out of Scope

  • Network creation/destruction — handled by S002
  • Bridge management — handled by S001
  • Rule evaluation — handled by S003
  • Agent API protocol — handled by S004
  • Shim binary behavior — handled by S005
  • HTTP proxy behavior — handled by S006
  • DNS filter behavior — handled by S007
  • Container orchestration (scheduling, scaling, rolling updates)
  • Image building — operators provide pre-built images
  • Volume management beyond the required bind mounts
  • TLS or authentication on the host socket
  • Inter-container networking beyond outcall network attachment
  • GPU passthrough or device mounting

Cross-Spec Dependencies

  • Depends on: S001 (bridge must be up)
  • Depends on: S002 (network must exist for container attachment)
  • Depends on: S004 (agent.sock path for bind mount)
  • Depends on: S005 (shim binary path for bind mount)
  • Depends on: S006 (proxy address for env var injection)
  • Depends on: S007 (DNS resolver address for container config)
  • Required by: Host operators managing agent workloads

Shared Types (outcall-api)

Constants

pub const CONTAINER_PREFIX: &str = "outcall-agent-";
pub const SHIM_CONTAINER_PATH: &str = "/usr/local/bin/outcall";
pub const AGENT_SOCK_CONTAINER_PATH: &str = "/run/outcall/agent.sock";
pub const DEFAULT_STOP_TIMEOUT_SECS: u32 = 10;
pub const DEFAULT_MEMORY_LIMIT: u64 = 512 * 1024 * 1024; // 512 MiB
pub const DEFAULT_CPU_SHARES: u64 = 1024;

Types

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerCreateRequest {
    pub image: String,
    pub network: Option<String>,
    pub name: Option<String>,
    pub memory_limit: Option<u64>,
    pub cpu_shares: Option<u64>,
    pub env: Option<Vec<String>>,
    pub cmd: Option<Vec<String>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerCreateResult {
    pub container_id: String,
    pub name: String,
    pub created: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerInfo {
    pub container_id: String,
    pub name: String,
    pub image: String,
    pub state: String,
    pub network: String,
    pub created_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerStopRequest {
    pub name: String,
    pub timeout: Option<u32>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerStopResult {
    pub name: String,
    pub stopped: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerRemoveRequest {
    pub name: String,
    pub force: Option<bool>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerRemoveResult {
    pub name: String,
    pub removed: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContainerInspectResult {
    pub container_id: String,
    pub name: String,
    pub image: String,
    pub state: String,
    pub network: String,
    pub ip_address: Option<String>,
    pub mounts: Vec<String>,
    pub env: Vec<String>,
    pub created_at: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImagePullRequest {
    pub image: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImagePullResult {
    pub image: String,
    pub pulled: bool,
}

On this page