S3-compatible proxy with transparent delta compression using xdelta3
2
stars
1,405
commits
Rust
primary language
Sep 15, 2026
updated
Not another object store or storage cluster: DeltaGlider is the S3 control plane in front of the storage you already run. It routes buckets across existing backends and local filesystems, adds a proper, centralized admin UI for IAM, OAuth, lifecycle, replication, event outbox delivery, audits, caching, and encryption, and reduces storage growth for repeated binaries with xdelta3 deltas. One binary. One port. Existing S3 workflows.
Organizations run storage across multiple providers — AWS S3, lower-cost S3-compatible SaaS, Hetzner Object Storage, Backblaze B2, MinIO, local NFS. Each has its own credentials, endpoints, and access policies. Teams share credentials in Slack. There's no audit trail. No prefix-level access control. No way to publish a folder without exposing the whole bucket.
DeltaGlider Proxy solves this by sitting in front of all your backends and presenting a single, authenticated S3 endpoint. It is not trying to be the distributed object store; it is the policy, routing, cache, lifecycle, replication, event, audit, encryption, and compression layer operators usually have to stitch together around one:
┌──────────────────────┐
┌───▶│ AWS S3 (us-east-1) │
┌──────────────┐ ┌─────────────┐ │ └──────────────────────┘
│ S3 clients │───▶│ DeltaGlider │──┤ ┌──────────────────────┐
│ (unchanged) │ │ Proxy │──┼───▶│ Hetzner (Helsinki) │
└──────────────┘ └─────────────┘ │ └──────────────────────┘
│ ┌──────────────────────┐
└───▶│ Local filesystem │
└──────────────────────┘
Clients see standard S3. They don't know which backend stores their bucket. They don't know repeated binaries are stored as compact deltas or encrypted before an untrusted backend sees them. They authenticate once — with corporate SSO if you want — and the proxy handles the rest.



*@company.com), glob patterns, regex, or identity provider claims. New hires get the right access on first login.
PUT releases/v2.zip ──▶ DeltaGlider ──▶ stored as 1.4MB delta (was 82MB)
GET releases/v2.zip ──▶ DeltaGlider ──▶ reconstructed, streamed back as 82MB
Everything managed from a web UI served on the same port as the S3 API — no extra containers, no extra infrastructure:



The proxy refuses to start without credentials (preventing accidentally open deployments). Supply them or explicitly opt into open access:
docker run -p 9000:9000 \
-e DGP_ACCESS_KEY_ID=admin \
-e DGP_SECRET_ACCESS_KEY=changeme \
beshultd/deltaglider_proxy
Then point any S3 client at http://localhost:9000:
export AWS_ENDPOINT_URL=http://localhost:9000
aws s3 mb s3://builds
aws s3 cp v1.zip s3://builds/releases/v1.zip
aws s3 cp v2.zip s3://builds/releases/v2.zip # stored as delta
aws s3 cp s3://builds/releases/v2.zip ./v2.zip # full file back, byte-identical
Admin GUI at http://localhost:9000/_/ — same port, zero setup. On first run, the bootstrap password is auto-generated and printed to stderr; override with DGP_BOOTSTRAP_PASSWORD_HASH or the --set-bootstrap-password flag.
YAML config file (canonical) or environment variables (DGP_* prefix). A five-line config is runnable:
# deltaglider_proxy.yaml
storage:
s3: https://s3.example.com
access_key_id: admin
secret_access_key: changeme
The canonical format has four optional top-level sections — admission, access, storage, advanced — each independently optional. Fields equal to their defaults are omitted from exports to keep GitOps diffs small.
admission:
blocks:
- name: deny-bad-ips
match:
source_ip_list: ["203.0.113.0/24"]
action: deny
access:
access_key_id: admin
secret_access_key: changeme
# iam_mode: gui # (default) encrypted IAM DB is source of truth
# iam_mode: declarative # YAML owns IAM; admin-API mutations return 403
storage:
default_backend: primary
backends:
- name: primary
type: s3
endpoint: https://s3.us-east-1.amazonaws.com
region: us-east-1
- name: europe
type: s3
endpoint: https://hel1.your-objectstorage.com
region: hel1
buckets:
releases:
backend: europe
compression: true
public_prefixes: ["builds/", "artifacts/"]
quota_bytes: 10737418240
docs-site:
public: true # shorthand for public_prefixes: [""]
archive:
backend: primary
alias: prod-archive-2024
compression: false
advanced:
cache_size_mb: 2048
log_level: deltaglider_proxy=info,tower_http=warn
Offline validation — run before committing to CI:
deltaglider_proxy config lint deltaglider_proxy.yaml
TOML support was removed in v1.4.1 — YAML is the only config format. A .toml config makes the proxy fail at startup with an actionable error. Still on TOML? Run deltaglider_proxy config migrate on v1.4.0 to convert, then upgrade. See How to upgrade the proxy.
Example: deltaglider_proxy.example.yaml.
Admin API for GitOps — full-document apply, per-section PATCH (RFC 7396 merge-patch), JSON Schema export, and an admission-chain trace endpoint. See the admin API reference.
| Operations | |
|---|---|
| Objects | PutObject, GetObject, HeadObject, DeleteObject, CopyObject |
| Listing | ListObjectsV2 (start-after, encoding-type, fetch-owner, continuation tokens) |
| Buckets | CreateBucket, HeadBucket, DeleteBucket, ListBuckets |
| Multipart | Create, UploadPart, Complete, Abort, ListParts, ListUploads |
| Auth | SigV4 header + presigned URLs, per-user IAM, OAuth/OIDC, public prefixes |
| Conditional | If-Match, If-None-Match (304), If-Modified-Since, If-Unmodified-Since (412) |
| Range | Range requests (206 Partial Content) |
| Validation | Content-MD5 on PUT/UploadPart |
| Lifecycle | Expiration and transition/archive rules via scheduler, preview, run-now, pause/resume, and history/failures |
Not implemented: versioning, storage-class transitions, object lock.
Single Rust binary. Async throughout (Tokio + axum). Single port serves S3 API on / and admin UI + APIs under /_/.
S3 request
→ SigV4 auth / OAuth session / public prefix bypass
→ IAM authorization (ABAC with conditions)
→ Multi-backend routing (virtual bucket → real backend + bucket)
→ FileRouter (delta-eligible vs passthrough)
→ DeltaGlider engine (compress / reconstruct / cache)
→ StorageBackend (filesystem, S3, or routed)
Multi-arch images (amd64 + arm64) published on every release:
docker run -p 9000:9000 beshultd/deltaglider_proxy
The chart lives in charts/deltaglider-proxy:
helm upgrade --install dgp ./charts/deltaglider-proxy \
--namespace dgp \
--create-namespace
Port-forward:
kubectl -n dgp port-forward svc/dgp-deltaglider-proxy 9000:9000
Open the admin UI at http://127.0.0.1:9000/_/.
The default development bootstrap password is change-me-in-production; do not expose that install outside localhost. For production, create a Kubernetes Secret outside Helm with stable DGP_ACCESS_KEY_ID, DGP_SECRET_ACCESS_KEY, DGP_BOOTSTRAP_PASSWORD_HASH, and any backend credentials, then install with:
helm upgrade --install dgp ./charts/deltaglider-proxy \
--namespace dgp \
--create-namespace \
--set auth.createSecret=false \
--set auth.existingSecret=deltaglider-secrets
The chart mounts config at /data/deltaglider_proxy.yaml so the encrypted IAM DB is created at /data/deltaglider_config.db on the PVC. Full guide: How to deploy on Kubernetes with Helm.
For deployments with more than one pod, use the official operator in operator/. It manages the proxy pods plus the consistent-hashing router that multi-pod S3 traffic requires. Without that router, multipart uploads fail with NoSuchUpload behind a round-robin Service, because the state of an upload lives only on the pod that started it. The operator README states the trade-offs explicitly. Guide: How to scale out with the Kubernetes operator.
Operator-facing docs are also bundled into the running binary at /_/docs/. Source files:
The docs follow Diátaxis — every page is exactly one of tutorial / how-to / reference / explanation.
Tutorials (guided lessons):
How-to guides (goal-named recipes): take a proxy to production · Docker Compose · Kubernetes · Kubernetes operator · TLS · upgrade · back up & restore · HA · monitor · trace & audit · troubleshooting · route a bucket · migrate data in · move a bucket · compression & quotas · replicate · expire & archive · encrypt · rotate keys · events · IAM users · conditions · SSO · IAM as code · admission rules · public folders
Reference (pure facts):
Concepts (how it works and why):
Plus the FAQ index.
Contributor-only (not in the binary):
Business Source License 1.1 (BUSL-1.1). In plain terms:
Contributors must sign the Contributor License Agreement, which assigns copyright to Beshu Limited so the project can be licensed this way. A bot will prompt you to sign on your first pull request.
Rust
93.0%
Astro
2.7%
TypeScript
2.0%
CSS
1.1%
Shell
1.0%
S3-compatible proxy with transparent delta compression using xdelta3
2
stars
1,405
commits
Rust
primary language
Sep 15, 2026
updated
Not another object store or storage cluster: DeltaGlider is the S3 control plane in front of the storage you already run. It routes buckets across existing backends and local filesystems, adds a proper, centralized admin UI for IAM, OAuth, lifecycle, replication, event outbox delivery, audits, caching, and encryption, and reduces storage growth for repeated binaries with xdelta3 deltas. One binary. One port. Existing S3 workflows.
Organizations run storage across multiple providers — AWS S3, lower-cost S3-compatible SaaS, Hetzner Object Storage, Backblaze B2, MinIO, local NFS. Each has its own credentials, endpoints, and access policies. Teams share credentials in Slack. There's no audit trail. No prefix-level access control. No way to publish a folder without exposing the whole bucket.
DeltaGlider Proxy solves this by sitting in front of all your backends and presenting a single, authenticated S3 endpoint. It is not trying to be the distributed object store; it is the policy, routing, cache, lifecycle, replication, event, audit, encryption, and compression layer operators usually have to stitch together around one:
┌──────────────────────┐
┌───▶│ AWS S3 (us-east-1) │
┌──────────────┐ ┌─────────────┐ │ └──────────────────────┘
│ S3 clients │───▶│ DeltaGlider │──┤ ┌──────────────────────┐
│ (unchanged) │ │ Proxy │──┼───▶│ Hetzner (Helsinki) │
└──────────────┘ └─────────────┘ │ └──────────────────────┘
│ ┌──────────────────────┐
└───▶│ Local filesystem │
└──────────────────────┘
Clients see standard S3. They don't know which backend stores their bucket. They don't know repeated binaries are stored as compact deltas or encrypted before an untrusted backend sees them. They authenticate once — with corporate SSO if you want — and the proxy handles the rest.



*@company.com), glob patterns, regex, or identity provider claims. New hires get the right access on first login.
PUT releases/v2.zip ──▶ DeltaGlider ──▶ stored as 1.4MB delta (was 82MB)
GET releases/v2.zip ──▶ DeltaGlider ──▶ reconstructed, streamed back as 82MB
Everything managed from a web UI served on the same port as the S3 API — no extra containers, no extra infrastructure:



The proxy refuses to start without credentials (preventing accidentally open deployments). Supply them or explicitly opt into open access:
docker run -p 9000:9000 \
-e DGP_ACCESS_KEY_ID=admin \
-e DGP_SECRET_ACCESS_KEY=changeme \
beshultd/deltaglider_proxy
Then point any S3 client at http://localhost:9000:
export AWS_ENDPOINT_URL=http://localhost:9000
aws s3 mb s3://builds
aws s3 cp v1.zip s3://builds/releases/v1.zip
aws s3 cp v2.zip s3://builds/releases/v2.zip # stored as delta
aws s3 cp s3://builds/releases/v2.zip ./v2.zip # full file back, byte-identical
Admin GUI at http://localhost:9000/_/ — same port, zero setup. On first run, the bootstrap password is auto-generated and printed to stderr; override with DGP_BOOTSTRAP_PASSWORD_HASH or the --set-bootstrap-password flag.
YAML config file (canonical) or environment variables (DGP_* prefix). A five-line config is runnable:
# deltaglider_proxy.yaml
storage:
s3: https://s3.example.com
access_key_id: admin
secret_access_key: changeme
The canonical format has four optional top-level sections — admission, access, storage, advanced — each independently optional. Fields equal to their defaults are omitted from exports to keep GitOps diffs small.
admission:
blocks:
- name: deny-bad-ips
match:
source_ip_list: ["203.0.113.0/24"]
action: deny
access:
access_key_id: admin
secret_access_key: changeme
# iam_mode: gui # (default) encrypted IAM DB is source of truth
# iam_mode: declarative # YAML owns IAM; admin-API mutations return 403
storage:
default_backend: primary
backends:
- name: primary
type: s3
endpoint: https://s3.us-east-1.amazonaws.com
region: us-east-1
- name: europe
type: s3
endpoint: https://hel1.your-objectstorage.com
region: hel1
buckets:
releases:
backend: europe
compression: true
public_prefixes: ["builds/", "artifacts/"]
quota_bytes: 10737418240
docs-site:
public: true # shorthand for public_prefixes: [""]
archive:
backend: primary
alias: prod-archive-2024
compression: false
advanced:
cache_size_mb: 2048
log_level: deltaglider_proxy=info,tower_http=warn
Offline validation — run before committing to CI:
deltaglider_proxy config lint deltaglider_proxy.yaml
TOML support was removed in v1.4.1 — YAML is the only config format. A .toml config makes the proxy fail at startup with an actionable error. Still on TOML? Run deltaglider_proxy config migrate on v1.4.0 to convert, then upgrade. See How to upgrade the proxy.
Example: deltaglider_proxy.example.yaml.
Admin API for GitOps — full-document apply, per-section PATCH (RFC 7396 merge-patch), JSON Schema export, and an admission-chain trace endpoint. See the admin API reference.
| Operations | |
|---|---|
| Objects | PutObject, GetObject, HeadObject, DeleteObject, CopyObject |
| Listing | ListObjectsV2 (start-after, encoding-type, fetch-owner, continuation tokens) |
| Buckets | CreateBucket, HeadBucket, DeleteBucket, ListBuckets |
| Multipart | Create, UploadPart, Complete, Abort, ListParts, ListUploads |
| Auth | SigV4 header + presigned URLs, per-user IAM, OAuth/OIDC, public prefixes |
| Conditional | If-Match, If-None-Match (304), If-Modified-Since, If-Unmodified-Since (412) |
| Range | Range requests (206 Partial Content) |
| Validation | Content-MD5 on PUT/UploadPart |
| Lifecycle | Expiration and transition/archive rules via scheduler, preview, run-now, pause/resume, and history/failures |
Not implemented: versioning, storage-class transitions, object lock.
Single Rust binary. Async throughout (Tokio + axum). Single port serves S3 API on / and admin UI + APIs under /_/.
S3 request
→ SigV4 auth / OAuth session / public prefix bypass
→ IAM authorization (ABAC with conditions)
→ Multi-backend routing (virtual bucket → real backend + bucket)
→ FileRouter (delta-eligible vs passthrough)
→ DeltaGlider engine (compress / reconstruct / cache)
→ StorageBackend (filesystem, S3, or routed)
Multi-arch images (amd64 + arm64) published on every release:
docker run -p 9000:9000 beshultd/deltaglider_proxy
The chart lives in charts/deltaglider-proxy:
helm upgrade --install dgp ./charts/deltaglider-proxy \
--namespace dgp \
--create-namespace
Port-forward:
kubectl -n dgp port-forward svc/dgp-deltaglider-proxy 9000:9000
Open the admin UI at http://127.0.0.1:9000/_/.
The default development bootstrap password is change-me-in-production; do not expose that install outside localhost. For production, create a Kubernetes Secret outside Helm with stable DGP_ACCESS_KEY_ID, DGP_SECRET_ACCESS_KEY, DGP_BOOTSTRAP_PASSWORD_HASH, and any backend credentials, then install with:
helm upgrade --install dgp ./charts/deltaglider-proxy \
--namespace dgp \
--create-namespace \
--set auth.createSecret=false \
--set auth.existingSecret=deltaglider-secrets
The chart mounts config at /data/deltaglider_proxy.yaml so the encrypted IAM DB is created at /data/deltaglider_config.db on the PVC. Full guide: How to deploy on Kubernetes with Helm.
For deployments with more than one pod, use the official operator in operator/. It manages the proxy pods plus the consistent-hashing router that multi-pod S3 traffic requires. Without that router, multipart uploads fail with NoSuchUpload behind a round-robin Service, because the state of an upload lives only on the pod that started it. The operator README states the trade-offs explicitly. Guide: How to scale out with the Kubernetes operator.
Operator-facing docs are also bundled into the running binary at /_/docs/. Source files:
The docs follow Diátaxis — every page is exactly one of tutorial / how-to / reference / explanation.
Tutorials (guided lessons):
How-to guides (goal-named recipes): take a proxy to production · Docker Compose · Kubernetes · Kubernetes operator · TLS · upgrade · back up & restore · HA · monitor · trace & audit · troubleshooting · route a bucket · migrate data in · move a bucket · compression & quotas · replicate · expire & archive · encrypt · rotate keys · events · IAM users · conditions · SSO · IAM as code · admission rules · public folders
Reference (pure facts):
Concepts (how it works and why):
Plus the FAQ index.
Contributor-only (not in the binary):
Business Source License 1.1 (BUSL-1.1). In plain terms:
Contributors must sign the Contributor License Agreement, which assigns copyright to Beshu Limited so the project can be licensed this way. A bot will prompt you to sign on your first pull request.
Rust
93.0%
Astro
2.7%
TypeScript
2.0%
CSS
1.1%
Shell
1.0%