Bunker meets Docker - encrypted delta sync for air-gapped builds.
Buncker analyzes Dockerfiles, identifies missing layers in your offline store, and transfers only the delta via encrypted USB - no bulk snapshots, no internet fallback, no magic.
Nothing equivalent exists: Hauler does bulk snapshot & ship but has no Dockerfile resolver and no delta approach.
Buncker supports two operating modes depending on your setup:
- Direct mode - the operator works directly on the buncker server via CLI
- LAN client mode - the operator works from any machine on the offline LAN via
curl+ Bearer tokens (no SSH needed)
Both modes use the same transfer pipeline: encrypted request out, blobs fetched online, encrypted response back.
OFFLINE (isolated LAN) USB ONLINE (connected machine)
┌───────────────────────┐ ┌────────────────┐ ┌──────────────────────────┐
│ buncker daemon │ │ request.enc │ │ buncker-fetch CLI │
│ │───>│ (AES-256-GCM) │───>│ │
│ 1. Analyze Dockerfile│ └────────────────┘ │ 3. Decrypt + verify │
│ 2. Diff missing blobs│ │ 4. Fetch delta blobs │
│ │ ┌────────────────┐ │ 5. Build response │
│ 7. Import + verify │<───│ response.enc │<───│ │
│ 8. Serve via OCI API │ │ (AES-256-GCM) │ └──────────────────────────┘
└───────────────────────┘ └────────────────┘
| Feature | Description |
|---|---|
| Delta sync | Only missing layers are transferred, not entire images |
| Dockerfile resolver | Static analysis of FROM, ARG, multi-stage, multi-arch |
| Encrypted transfers | AES-256-GCM + HMAC-SHA256 on all USB files |
| BIP-39 mnemonic | 16-word shared secret, no PKI to manage |
| OCI standard | Local registry compatible with docker pull, no client changes |
| Full audit trail | Every operation logged in structured JSON Lines |
| Compose support | Analyze docker-compose.yml to resolve all service images at once |
| Manifest staleness | Configurable TTL warns when cached manifests are outdated, --refresh-stale re-fetches them |
| OS-packaged deps only | Python stdlib + python3-cryptography + python3-yaml (apt/dnf) |
| Component | Role | Packaging |
|---|---|---|
buncker |
Offline HTTP daemon - OCI registry + admin API | .deb / .rpm (systemd) |
buncker-fetch |
Online CLI - fetch blobs from public registries | .deb / .rpm |
- Debian 12+ / Ubuntu 22.04+ or RHEL 9+ / Fedora 38+
- Python >= 3.11
python3-cryptographyandpython3-yaml(installed via apt/dnf, not pip)
Windows / macOS: Buncker requires Linux. On Windows, use WSL2 with a Debian or Ubuntu distribution. There is no native Windows or macOS build.
Download the latest .deb files from GitHub Releases:
# Offline machine
sudo dpkg -i buncker_1.0.3_all.deb
# Online machine
sudo dpkg -i buncker-fetch_1.0.3_all.debIf dependencies are missing, fix them with:
sudo apt-get install -fDownload the latest .rpm files from GitHub Releases:
# Offline machine
sudo dnf install buncker-1.0.3-1.noarch.rpm
# Online machine
sudo dnf install buncker-fetch-1.0.3-1.noarch.rpmgit clone https://github.com/Rwx-G/Buncker.git
cd Buncker
pip install ruff pytest cryptography pyyaml # dev dependencies
make build-deb # build .deb packages to dist/
sudo dpkg -i dist/buncker_*_all.deb
sudo dpkg -i dist/buncker-fetch_*_all.debThis step is the same for both operating modes.
sudo buncker setupExpected output:
[1/4] Generating cryptographic keys... done
[2/4] Initializing store... done
[3/4] Saving configuration... done
[4/4] Enabling and starting daemon... done
============================================================
IMPORTANT - Write down your 16-word recovery mnemonic.
This is the ONLY time it will be displayed.
pride evoke tumble stool coach enact lazy ribbon
silent split orphan peace flavor broom render desk
Config: /etc/buncker/config.json
Store: /var/lib/buncker
Daemon: active on 127.0.0.1:5000
============================================================
Setup automatically enables and starts the daemon via systemd. The mnemonic
is also saved to /etc/buncker/env (mode 0600) so the service can restart
without manual re-entry.
The operator works directly on the buncker server. Transfers go through USB.
1. Prepare a transfer request from a Dockerfile
buncker prepare ./Dockerfile --build-arg NODE_VERSION=20 --output /media/usb/
# Combines analyze + generate-manifest in one stepThis analyzes the Dockerfile, identifies missing layers, and writes the
encrypted transfer request to the USB drive in a single command.
You can also run buncker analyze and buncker generate-manifest separately
if you need to inspect the analysis before generating the request.
2. Online machine - pair and fetch
buncker-fetch pair
# Enter the 16-word mnemonic when prompted
buncker-fetch fetch /media/usb/request.json.enc --output /media/usb/3. Back offline - import and build
buncker import /media/usb/buncker-response.tar.enc
docker build -t myapp . # works without internetThe operator works from any machine on the isolated LAN - no SSH to the buncker server needed. All admin operations go through HTTP with Bearer token authentication.
1. Enable API auth on the buncker server
sudo buncker api-setup
# Generates admin + readonly tokens, activates TLS
# Displays cert fingerprint and copies CA to /etc/buncker/ca.pem
# Distribute CA certificate to LAN clients
scp /etc/buncker/ca.pem user@client:~/buncker-ca.pem
# Show the admin token (if needed later)
buncker api-show admin2. From LAN client - analyze and generate transfer request
# Analyze a Dockerfile (send content, not a file path)
curl -X POST -H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"dockerfile_content": "FROM alpine:3.19\nRUN apk add curl"}' \
https://buncker:5000/admin/analyze --cacert buncker-ca.pem
# Download the encrypted transfer request
curl -X POST -H "Authorization: Bearer <admin-token>" \
-o request.json.enc \
https://buncker:5000/admin/generate-manifest --cacert buncker-ca.pem3. Online machine - pair and fetch (same as direct mode)
buncker-fetch pair
# Enter the 16-word mnemonic when prompted
buncker-fetch fetch request.json.enc --output ./4. From LAN client - upload response with checksum verification
CHECKSUM=$(sha256sum buncker-response.tar.enc | cut -d' ' -f1)
curl -T buncker-response.tar.enc \
-H "Authorization: Bearer <admin-token>" \
-H "X-Buncker-Checksum: sha256:$CHECKSUM" \
https://buncker:5000/admin/import --cacert buncker-ca.pem5. Build (from any Docker host on the LAN)
docker build -t myapp . # pulls from buncker registry, no internetCheck status (read-only token is sufficient):
curl -H "Authorization: Bearer <readonly-token>" \
https://buncker:5000/admin/status --cacert buncker-ca.pem| Side | Config file |
|---|---|
| Offline daemon | /etc/buncker/config.json |
| Online CLI | ~/.buncker/config.json |
| Docker clients | See Docker Client Setup below |
Docker clients on the offline LAN need to pull images from the buncker registry instead of Docker Hub. There are two approaches:
Approach 1 - Explicit registry in Dockerfiles (simplest)
Reference the buncker host directly in your FROM instructions:
FROM buncker-host:5000/library/alpine:3.19
FROM buncker-host:5000/library/python:3.11-slimAdd the registry as insecure (HTTP) in /etc/docker/daemon.json:
{
"insecure-registries": ["buncker-host:5000"]
}Restart Docker after editing: sudo systemctl restart docker
This is the most reliable approach. Replace buncker-host with the
actual hostname or IP of the buncker daemon on your offline LAN.
Approach 2 - Registry mirror (transparent, Docker 20.10+)
Configure Docker to use buncker as a pull-through mirror.
Dockerfiles keep standard FROM alpine:3.19 syntax.
In /etc/docker/daemon.json:
{
"registry-mirrors": ["http://buncker-host:5000"],
"insecure-registries": ["buncker-host:5000"]
}Restart Docker after editing: sudo systemctl restart docker
With this setup, docker pull alpine:3.19 checks buncker first.
Note: registry mirrors only work for Docker Hub (docker.io) images.
Images from other registries (ghcr.io, quay.io) still need explicit
references as in Approach 1.
| Field | Type | Default | Description |
|---|---|---|---|
source_id |
string | "" |
Unique identifier for this buncker instance |
bind |
string | "127.0.0.1" |
Listen address (localhost only; api-setup switches to 0.0.0.0 for LAN access) |
port |
int | 5000 |
Listen port |
store_path |
string | "/var/lib/buncker" |
OCI blob store directory |
max_workers |
int | 16 |
Thread pool size for HTTP server |
tls |
bool | false |
Enable HTTPS (self-signed CA) |
crypto.salt |
string | - | Base64-encoded PBKDF2 salt (set by buncker setup) |
crypto.mnemonic_hash |
string | - | SHA256 hash of mnemonic for verification |
private_registries |
list | [] |
Private registry patterns to skip |
gc.inactive_days_threshold |
int | 90 |
GC inactivity threshold in days |
log_level |
string | "INFO" |
Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
transfer_path |
string | "" |
Default directory for transfer files (empty = cwd) |
manifest_ttl |
int | 30 |
Days before cached manifests are considered stale |
oci.restrict |
bool | false |
Require Bearer token on /v2/* OCI endpoints |
| Field | Type | Description |
|---|---|---|
salt |
string | Base64-encoded PBKDF2 salt (set by buncker-fetch pair) |
derived_key_check |
string | Encrypted marker for mnemonic verification |
transfer_path |
string | Default directory for transfer files (empty = cwd) |
| Command | Description |
|---|---|
buncker setup |
Initialize: generate keys, create config, init store, start daemon |
buncker serve |
Start the HTTP daemon (reads mnemonic from BUNCKER_MNEMONIC env or stdin) |
buncker prepare <Dockerfile> |
Analyze + generate transfer request in one step |
buncker analyze <Dockerfile> |
Analyze Dockerfile and identify missing blobs |
buncker analyze --compose <file> |
Analyze docker-compose.yml and resolve all service images |
buncker generate-manifest |
Generate an encrypted transfer request |
buncker import [file.tar.enc] |
Import an encrypted transfer response (auto-scans transfer_path if omitted, --cleanup deletes file after success) |
buncker status |
Show registry status (blob count, store size) |
buncker verify |
Re-hash all blobs and detect silent corruption (bit-rot) |
buncker gc --report |
List inactive blobs eligible for garbage collection |
buncker gc --execute |
Delete reported inactive blobs (requires --yes or interactive confirmation) |
buncker rotate-keys |
Generate a new mnemonic and deprecate old keys |
buncker export-ca |
Print CA certificate to stdout (TLS mode only) |
buncker api-setup |
Generate API tokens and activate TLS for LAN access |
buncker api-show readonly|admin |
Display an API token |
buncker api-reset readonly|admin |
Regenerate an API token |
Flags:
| Flag | Description |
|---|---|
--config <path> |
Config file path (default: /etc/buncker/config.json) |
--cleanup |
Delete .tar.enc file after successful import |
--build-arg KEY=VALUE |
Build argument for analyze and prepare (repeatable) |
--output <path> |
Output directory for generate-manifest and prepare |
--inactive-days N |
GC inactivity threshold (default: 90) |
--compose <file> |
Docker Compose file for analyze (resolves all services) |
--restrict-oci |
Require Bearer token on /v2/* OCI endpoints (serve) |
--refresh-stale |
Include stale manifests for re-download (generate-manifest) |
--operator <name> |
Operator name for GC audit trail |
--grace-period N |
Key rotation grace period in days (default: 30) |
--cert <path> |
TLS certificate for api-setup |
--key <path> |
TLS private key for api-setup |
| Command | Description |
|---|---|
buncker-fetch pair |
Enter 16-word mnemonic and derive encryption keys |
buncker-fetch inspect <file.json.enc> |
Decrypt and display transfer request summary |
buncker-fetch fetch [file.json.enc] |
Fetch missing blobs and build encrypted response (auto-scans transfer_path if omitted) |
buncker-fetch status |
Display cache statistics |
buncker-fetch cache clean |
Remove old cached blobs |
Flags:
| Flag | Description |
|---|---|
--json |
Machine-readable JSON output |
--config <path> |
Config file path (default: ~/.buncker/config.json) |
--output <path> |
Output directory for fetch response |
--parallelism N |
Parallel downloads for fetch (default: 4) |
--older-than Nd |
Cache clean threshold (default: 30d) |
--deb <path> |
Include a .deb update file in the fetch response |
Generate a new mnemonic when compromised or as periodic security practice:
sudo buncker rotate-keys --grace-period 30
# Write down the new 16-word mnemonic
# Restart daemon with new mnemonic
sudo systemctl restart buncker
# Re-pair online machine
buncker-fetch pairOld keys remain valid during the grace period for in-flight transfers.
Remove blobs that have not been referenced in recent transfers:
# Preview candidates
buncker gc --report --inactive-days 90
# Execute cleanup
buncker gc --execute --operator "admin"On the online machine, manage the local blob cache:
# Check cache usage
buncker-fetch status
# Clean blobs older than 30 days
buncker-fetch cache clean --older-than 30dBuncker writes structured JSON Lines logs:
# Daemon logs
sudo tail -f /var/log/buncker/buncker.log
# Or via journalctl
sudo journalctl -u buncker -fbuncker setup encrypts the mnemonic with a PBKDF2-derived key from
/etc/machine-id and stores the ciphertext as BUNCKER_MNEMONIC_ENC=<base64>
in /etc/buncker/env (mode 0600, owned by root). The daemon decrypts it
automatically on startup. This prevents direct exposure if the file is
read by an attacker without access to the machine-id.
On sensitive deployments, consider additional protections:
- LUKS encryption - place the buncker data directory on a LUKS encrypted partition for full at-rest protection
- TPM-backed encryption - use
systemd-credsorclevisto seal/etc/buncker/envto the machine's TPM, so it can only be decrypted on that specific host - Manual entry - remove
/etc/buncker/envand enter the mnemonic via stdin on each daemon start (setBUNCKER_MNEMONICenv or pipe it). This provides the strongest protection but requires manual intervention on every restart
Buncker state consists of the blob store and configuration files. To back up:
# Back up store and config
rsync -a /var/lib/buncker/ /backup/buncker-store/
rsync -a /etc/buncker/ /backup/buncker-config/To restore, copy the files back and restart the daemon.
Mnemonic recovery: if the mnemonic is lost and /etc/buncker/env is
unavailable, the only option is buncker rotate-keys to generate a new
mnemonic. There is no way to extract the original mnemonic from the config.
After rotation, re-pair the online machine with buncker-fetch pair.
By default, the OCI Distribution API endpoints (/v2/, /v2/<name>/manifests/,
/v2/<name>/blobs/) are unauthenticated, even when API auth is enabled.
Docker clients can pull images without Bearer token configuration.
For high-security environments, use --restrict-oci to require
authentication on OCI endpoints:
buncker serve --restrict-ociThis requires api-setup to be configured first (tokens + TLS). The
auto-generated certificate covers localhost, 127.0.0.1, and buncker
as SANs. For a custom hostname, provide your own certificate via
buncker api-setup --cert <path> --key <path>.
Docker clients authenticate via hosts.toml:
# /etc/docker/certs.d/buncker:5000/hosts.toml
server = "https://buncker:5000"
[host."https://buncker:5000"]
capabilities = ["pull"]
ca = "/path/to/ca.pem"
[host."https://buncker:5000".header]
Authorization = ["Bearer <readonly-token>"]When restricted, unauthenticated requests receive a 401 with a standard
WWW-Authenticate: Bearer challenge per the OCI Distribution Spec.
Default mode implications:
- Any machine on the offline LAN can pull images from buncker
- Image content is not confidential in most air-gapped scenarios (the threat model protects integrity and provenance, not secrecy)
- For additional access control without
--restrict-oci, use network-level controls (firewall rules, VLAN segmentation) to limit which hosts can reach port 5000
When API auth is enabled (buncker api-setup):
- TLS is mandatory (the daemon refuses to start without it)
- Admin endpoints require the admin token (analyze, import, GC execute)
- Read-only endpoints accept either token (status, logs, health, GC report)
- Token values are never logged (only
auth_levelappears in audit trail) - All admin API calls are logged with
client_ip,auth_level, anduser_agentfor forensic review - Per-IP rate limiting on admin endpoints: 60 requests/minute sliding window
(returns 429 with
Retry-Afterheader when exceeded) - Per-IP rate limiting on OCI endpoints (
/v2/*/manifests/,/v2/*/blobs/): 200 requests/minute sliding window
| Problem | Cause | Solution |
|---|---|---|
dpkg: dependency problems |
Missing Python or cryptography | sudo apt-get install -f |
Cannot connect to buncker daemon |
Daemon not running | sudo systemctl start buncker and check journalctl -u buncker |
mnemonic does not match config |
Wrong mnemonic entered | Re-enter the correct 16 words from initial setup |
Mnemonic verification failed on fetch |
Mnemonic or salt mismatch | Re-run buncker-fetch pair with the correct mnemonic |
buncker setup fails with "Config already exists" |
Previous setup detected | Back up and remove /etc/buncker/config.json to re-initialize |
| Blobs not found after import | OCI store path mismatch | Verify store_path in config matches daemon working directory |
docker build fails after import |
Daemon not serving imported blobs | Check buncker status and verify daemon is running |
| High disk usage on online machine | Blob cache growing | Run buncker-fetch cache clean --older-than 7d |
git clone https://github.com/Rwx-G/Buncker.git
cd Buncker
pip install ruff pytest cryptography
make lint # ruff check + format verification
make test # run pytest suite
make build-deb # build .deb packages to dist/
make build-rpm # build .rpm packages to dist/
make clean # remove build artifactsSee CONTRIBUTING.md for full development setup and guidelines.
| Document | Description |
|---|---|
| Architecture | Components, workflows, API spec, data models, tech stack |
| PRD | Requirements, epics, stories, success metrics |
| Contributing | Dev setup, commit convention, branching, testing |
| Changelog | Release history (Keep a Changelog + SemVer) |
| Security | Vulnerability reporting policy |
| Feature | Description | Status |
|---|---|---|
| Docker Compose support | buncker analyze --compose docker-compose.yml to extract image: and build.dockerfile from all services |
Done (v1.0.0) |
| RPM packaging | .rpm packages for RHEL/Fedora enterprise environments |
Done (v1.0.0) |
| Log rotation | logrotate.d/buncker config shipped in .deb/.rpm for /var/log/buncker/ |
Done (v1.0.0) |
| OCI auth restriction | --restrict-oci flag to require read-only token on /v2/* endpoints (high-security environments) |
Done (v1.0.0) |
| Manifest cache TTL | Configurable TTL (default 30d) on offline manifest cache with staleness warning and --refresh-stale flag |
Done (v1.0.0) |
Apache 2.0 - Copyright 2026 Rwx-G.