v1.0 — Now Available

P2P OCI Image Distribution
for Edge Computing

OutpostImage turns every edge node into a zero-overhead OCI registry by reusing the local Docker/containerd image store — enabling node-to-node image sharing at LAN speed, without extra storage.

Docker Native · Zero Storage Overhead · P2P Mesh · Offline First

Architecture

OutpostImage runs as a transparent HTTP proxy on every edge node.

┌──────────────────────────────────────────────────────────────────────┐ │ Edge Node │ │ │ │ containerd / dockerd / any OCI client │ │ │ HTTP_PROXY=http://localhost:3128 │ │ ▼ │ │ ┌─────────────────────────────────────┐ │ │ │ OutpostImage HTTP Proxy :3128 │ │ │ └──────────────┬──────────────────────┘ │ │ │ │ │ ┌─────────┴─────────┐ │ │ ▼ ▼ │ │ ┌───────────────┐ ┌──────────────────┐ │ │ │ Local Store │ │ P2P Peers │◄──── other edge nodes │ │ │ Docker / │ │ :5000 │ (mDNS or static) │ │ │ containerd │ │ registry API │ │ │ └───────────────┘ └──────────────────┘ │ │ │ │ cache miss ──────────────────────────────────────────────► │ └─────────────────────────────────────────────────────────────────────┘ ▼ 🌐 Upstream Registry (internet)

How It Works

OutpostImage intercepts image pulls, resolves from peers first, and shares back to the LAN — in three phases.

Phase 1 — Intercept: Docker or containerd is configured with a single env var: HTTP_PROXY=http://localhost:3128. All registry traffic flows through OutpostImage automatically — no mirror config, no daemon changes. For HTTPS registries, a locally-generated CA certificate enables transparent inspection.

Phase 2 — Resolve: For every blob or manifest, OutpostImage checks the local image store first, then queries P2P peers on the LAN via /api/v1/digests/{digest}. Only on a complete miss does it fall back to the upstream registry.

Phase 3 — Share: Once a node has an image, it becomes a source for every other node. Each instance exposes a standard OCI registry API on :5000, serving blobs directly from the local store. Peers discover each other via static config or mDNS auto-discovery — no central coordinator needed.

Edge Node A Edge Node B ┌─────────────────────┐ ┌─────────────────────┐ │ Docker / containerd │ │ Docker / containerd │ │ │ │ │ │ │ │ ▼ HTTP_PROXY │ │ ▼ HTTP_PROXY │ │ OutpostImage :3128 │◄──── P2P ──►│ OutpostImage :3128 │ │ │ │ :5000 │ │ │ │ ▼ │ │ ▼ │ │ Local Image Store │ │ Local Image Store │ └─────────────────────┘ └─────────────────────┘ │ │ └──── cache miss ────► Upstream Registry (internet)

Key Features

Built for edge deployments where bandwidth and storage are constrained.

🐳

Docker + containerd

Works with both Docker and containerd via HTTP proxy — no daemon config changes required.

💾

Zero Extra Storage

Reuses the existing Docker or containerd image store. Blobs are never duplicated on disk.

🌐

P2P Mesh Network

Static peer lists and mDNS auto-discovery build a LAN-speed mesh across all edge nodes.

🔌

HTTP Proxy Interception

A single HTTP_PROXY env var is all you need. Zero-intrusion deployment.

📊

Prometheus Metrics

Built-in metrics endpoint at :9090 plus /healthz and /readyz probes.

📝

OCI Distribution Spec

Fully compliant OCI registry API at :5000 — compatible with any OCI-conformant client.

📡

Multi-Registry

Works with Docker Hub, GHCR, ECR, and any other OCI-compatible registry simultaneously.

⚙️

Fully Configurable

All options available as CLI flags or environment variables. Hot-reloading peers file supported.

OutpostImage vs Spegel

Both solve P2P image distribution — OutpostImage targets broader runtime compatibility.

Feature Spegel OutpostImage
Runtime support containerd only Docker + containerd
Interception method containerd mirror config HTTP_PROXY env var
Extra storage None (containerd reuse) None (Docker/containerd reuse)
P2P discovery OCI / libp2p Static peers + mDNS
Multi-registry Yes Yes
Invasiveness Modifies containerd config Zero-intrusion (env var only)

Tested & Validated

Real-world validation across Docker and containerd on edge VMs (2026-03-30). All 5 value propositions passed. Full reports in docs/.

5/5
Value Props Passed
5/5
Store × Consumer Combos
260x
Cache Hit Speedup
11 MB
Static Binary

Test Environment

Two Ubuntu 20.04 KVM virtual machines on a Proxmox host, connected over a LAN bridge.

Node IP Role OS Docker containerd Hardware
VM 102 192.168.1.102 Image source + OutpostImage server Ubuntu 20.04.6 LTS 26.1.3 1.7.2 QEMU 1 vCPU, 2 GB RAM
VM 103 192.168.1.103 Image consumer Ubuntu 20.04.6 LTS 26.1.3 1.7.2 QEMU 1 vCPU, 2 GB RAM

OutpostImage configuration: Registry :15000, HTTP Proxy :13128, Metrics :19090. Raw TCP bandwidth between nodes: 5.4 Gbps.

Value Proposition Test Results

Value Proposition What Was Tested Evidence Result
VP-1 Zero-intrusion setup Single HTTP_PROXY env var, no daemon.json or containerd config changes All requests served locally; zero upstream traffic logged PASS
VP-2 Multi-registry transparent interception Pull from docker.io, ghcr.io, and registry.example.com in one session 3 registries intercepted; X-Original-Registry header routed correctly PASS
VP-3 Docker + containerd dual runtime 5 Store×Consumer combinations (docker/containerd/auto × docker pull / ctr pull) All 5 combinations pulled and unpacked successfully PASS
VP-4 Minimal dependencies Count direct deps, total transitive deps, binary size; compare vs Spegel 7 direct / 56 total deps; 11 MB static binary; ldd: not a dynamic executable PASS
VP-5 Offline-first Full network isolation via iptables DROP; pull images VM 102 → VM 103 Zero upstream registry references; containers ran successfully offline PASS

VP-1 Detail: Zero-Intrusion Setup

Configure Docker with a single proxy env var — no mirror config, no daemon changes needed.

# /etc/systemd/system/docker.service.d/proxy.conf
[Service]
Environment="HTTP_PROXY=http://192.168.1.102:13128"
Environment="HTTPS_PROXY=http://192.168.1.102:13128"

After restart, pulling redis:7-alpine and debian:bookworm-slim — all traffic served locally:

v2 version check served locally    source=local  path=/v2/
manifest served from local         source=local  path=/v2/library/redis/manifests/7-alpine
routing blob to local (direct)     source=local  path=/v2/library/redis/blobs/sha256:34c35a...
routing blob to local (direct)     source=local  path=/v2/library/debian/blobs/sha256:5712eb...
# All 8 layers + config blobs served locally — zero upstream requests

VP-2 Detail: Multi-Registry Transparent Interception

Three different registries pulled in a single session — no per-registry configuration required.

Image Pulled Registry Image ID Size Result
ghcr.io/test/debian:bookworm-slim GitHub Container Registry d1c8cb627495 78.6 MB PASS
registry.example.com/test/redis:7-alpine Custom registry e053f0daf05a 41.7 MB PASS
redis:7-alpine Docker Hub e053f0daf05a 41.7 MB (layers cached) PASS

VP-3 Detail: Docker × containerd Runtime Matrix

Complete Store × Consumer combination matrix — all 5 combinations verified.

# Store Mode Consumer Test Image Transfer Result
1 docker Docker (docker pull) redis:7-alpine manifest 200, library/ prefix normalized PASS
2 docker containerd (ctr pull) redis:7-alpine 40.6 MiB, all layers pulled & unpacked (3.5 s) PASS
3 containerd Docker (curl verification) debian:bookworm-slim OCI manifest returned; all blobs accessible PASS
4 auto (MultiStore) Docker (curl verification) redis:7-alpine Docker-store image resolved via MultiStore PASS
5 auto (MultiStore) containerd (ctr pull) debian:bookworm-slim 77.9 MiB pulled from containerd store, unpacked PASS
docker store initialized      images=2   tags=4   blobs=2   layers=9
containerd store initialized  images=22  tags=64  blobs=152
multiple stores detected, using MultiStore  count=2

VP-4 Detail: Minimal Dependencies

Compared against Spegel, the closest open-source alternative.

Metric OutpostImage Spegel (reference) Reduction
Direct dependencies 7 ~22 -68%
Total dependencies (incl. indirect) 56 ~130 -57%
Binary size 11 MB ~35–40 MB -72%
Dynamic linking None (fully static)
$ ldd outpostimage
        not a dynamic executable

VP-5 Detail: Offline-First Operation

Both VMs fully isolated from the internet via iptables; image sharing via LAN only.

# Applied on both VMs to cut external internet access
iptables -I OUTPUT -d 192.168.1.0/24 -j ACCEPT
iptables -I OUTPUT -d 127.0.0.0/8   -j ACCEPT
iptables -A OUTPUT -j DROP

# Verified external registry unreachable
$ curl --connect-timeout 3 https://registry-1.docker.io/v2/
curl: (28) Connection timed out

# Images pulled successfully over LAN
$ docker pull 192.168.1.102:15000/library/redis:7-alpine
Status: Downloaded newer image for 192.168.1.102:15000/library/redis:7-alpine

$ docker run --rm 192.168.1.102:15000/library/redis:7-alpine redis-server --version
Redis server v=7.4.7

Large Image Transfer Performance

Simulated edge AI model distribution between two QEMU VMs (1 vCPU, 2 GB RAM each). Raw TCP baseline: 5.4 Gbps / ~675 MB/s.

Scenario Image Size Transfer Rate Time Result
500 MB model (first pull) 603 MB 18.84 MB/s 32 s PASS
2 GB model (first pull) 2.23 GB 15.86 MB/s 144 s PASS
Repeat pull (Docker layer cache hit) 603 MB ∞ (local) 123 ms (260x faster) PASS
Parallel pull (500 MB + 2 GB simultaneous) 2.83 GB total 129 s (27% faster than serial 176 s) PASS

Transfer throughput is CPU-bound (real-time tar streaming from overlay2 diff dirs), not network-bound. Future optimization: pre-computed tar cache.

Startup & HTTPS Optimization Results

Fixes validated in v0.1.0 (commit bb232a0), tested on T480 (100-layer Docker store).

Optimization Before After Result
Startup time (100-layer Docker store) ~80 s (eager tar computation) 14 ms (lazy loading) PASS
First manifest request (lazy build included) N/A ~760 ms PASS
Subsequent manifest (cache hit) N/A <1 ms PASS
HTTPS MITM — CONNECT interception Not connected Dynamic cert generated; TLS decrypted PASS
Proxy mode: manifest/blob routing All blobs routed to upstream Local/P2P first, upstream only on miss PASS

Resource Footprint at Peak Transfer

5.8%
CPU (1 vCPU)
6 MB
RSS Memory
7
Direct Dependencies
0
Dynamic Libraries

Quick Start

Up and running in minutes on any Linux node.

# Download the latest binary
curl -L https://github.com/outpostos/outpostimage/releases/latest/download/outpostimage-linux-amd64 \
  -o /usr/local/bin/outpostimage && chmod +x /usr/local/bin/outpostimage

# Run with defaults (auto-detects Docker or containerd store)
outpostimage registry

# Run with explicit peers
outpostimage registry --peers node2:5000 --peers node3:5000
docker run -d \
  --name outpostimage \
  --network host \
  -v /var/lib/docker:/var/lib/docker:ro \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  outpostimage:latest registry
git clone https://github.com/outpostos/outpostimage
cd outpostimage

# Optional: create peers.txt with other node addresses
echo "192.168.1.101:5000" > peers.txt
echo "192.168.1.102:5000" >> peers.txt

docker compose up -d
# /etc/docker/daemon.json — point Docker at the proxy
{
  "proxies": {
    "http-proxy":  "http://localhost:3128",
    "https-proxy": "http://localhost:3128"
  }
}

# Trust the OutpostImage CA certificate
cp /etc/outpostimage/ca.crt /usr/local/share/ca-certificates/outpostimage-ca.crt
update-ca-certificates
Open Source & Free Forever

Ready to Distribute Images
at Edge Speed?

Deploy on your edge nodes in minutes. No central registry, no extra storage, no vendor lock-in.

Docker · containerd · OCI · Prometheus