Skip to main content

Architecture

Graft is a Go CLI that orchestrates three subsystems: the Docker lifecycle manager, the content-addressable storage (CAS) layer, and the state manager.

System Diagram

┌─────────────────────────────────────────────────────────┐
│                     Graft CLI                            │
│  ┌────────────┐  ┌────────────┐  ┌───────────────────┐  │
│  │ Docker      │  │ DAG        │  │ State Manager     │  │
│  │ Lifecycle   │  │ Store      │  │ (config.json)     │  │
│  └──────┬──────┘  └─────┬──────┘  └────────┬──────────┘  │
│         │               │                   │             │
│         ▼               ▼                   ▼             │
│  ┌────────────┐  ┌────────────┐  ┌───────────────────┐  │
│  │ Object     │  │ Merkle     │  │ Volume Bridge     │  │
│  │ Pool       │  │ Tree       │  │ (docker → host)   │  │
│  └────────────┘  └────────────┘  └───────────────────┘  │
└─────────────────────────────────────────────────────────┘
         │                        │
         ▼                        ▼
  ┌──────────┐            ┌──────────────┐
  │ Docker   │            │ Host         │
  │ Engine   │            │ Filesystem   │
  │ (API)    │            │ ~/.graft/    │
  └──────────┘            └──────────────┘

Subsystems

Docker Lifecycle

The internal/docker package wraps the Docker Engine SDK. It handles:
  • Container inspection (capturing image, env, ports, networks)
  • Graceful stop and start
  • Container removal and recreation with host bind mounts
  • Smart lifecycle (only restart if container was originally running)
Graft uses the Docker SDK directly (not shell commands) for type-safe, structured container management.

CAS Layer (DAG)

The internal/dag package contains three components:
ComponentFileResponsibility
Storestore.goSQLite CRUD for commits, tree_entries, refs
Merklemerkle.goBLAKE3 hashing, leaf building, tree construction
Objectobject.goBlob pool (Put, Get, Has, Materialize)
Verifyverify.goBranch integrity checking

State Manager

The internal/state package manages config.json — the lightweight state file at ~/.graft/<project>/. It tracks:
  • Schema version (for migration safety)
  • Target container ID
  • Active branch
  • Project root path
  • Operation mode (snapshot vs bind)
  • Container configuration (image, env, ports, network)
  • Branch metadata (name, path, size, timestamps)

Data Flow

Init Flow

1. Inspect container (image, env, ports, network, volumes)
2. Prompt for volume selection
3. Stop container
4. Bridge volume data to host (ephemeral Alpine runner)
5. Create objects/ directory + graft.db
6. (Optional) Recreate container with bind mount
7. Save config.json

Commit Flow

1. Stop container
2. Walk branch directory → BLAKE3 hash every file
3. Store each file in object pool (deduplicated)
4. Build Merkle tree → single root hash
5. Insert commit + tree entries in SQLite (atomic transaction)
6. Advance branch ref
7. Start container

Checkout Flow

1. Look up target branch commit
2. Load tree entries
3. Diff current dir vs target tree
4. Materialize only changed files from object pool
5. Prune empty directories
6. Recreate container with bind mount
7. Save config.json

Two Operation Modes

Snapshot Mode (default)

The container stays on its original Docker volume. At commit time, Graft bridges data from the Docker volume to the host using an ephemeral Alpine runner. The container is never recreated. Best for: Quick evaluation, when you don’t want Graft managing your container.

Bind Mount Mode (—bind)

Graft recreates the container with a host bind mount pointing at the branch directory. The container’s data lives directly on the host. No Alpine runner is needed for subsequent operations. Best for: Full lifecycle management, frequent branching, production use.