Skip to content

s3lo

Use object storage as a container image registry. AWS S3, Google Cloud Storage, Azure Blob, MinIO, Cloudflare R2 — faster pulls, cheaper storage, no registry to manage.

# Push to S3
s3lo push myapp:v1.0 s3://my-bucket/myapp:v1.0

# Push to GCS
s3lo push myapp:v1.0 gs://my-gcs-bucket/myapp:v1.0

# Push to Azure Blob
s3lo push myapp:v1.0 az://my-container/myapp:v1.0

# Push to MinIO or Cloudflare R2
s3lo push myapp:v1.0 s3://my-bucket/myapp:v1.0 --endpoint http://localhost:9000

# Mirror any image from Docker Hub, ECR, or GHCR directly to storage
s3lo copy alpine:latest s3://my-bucket/alpine:latest

Why s3lo?

Container registries are a solved problem — but ECR and Docker Hub are generic solutions. If you're already running on AWS, your S3 bucket is right next to your EC2 instances. Why go through a registry?

ECR s3lo + S3
Pull speed (EC2) ~1–5 Gbps Up to 100 Gbps
Storage cost $0.10/GB/month $0.023/GB/month
Deduplication None Bucket-wide, SHA256
Multi-arch Yes Yes (OCI Image Index)
Registry to manage Lifecycle policies, permissions, replication Just a bucket
Auth ECR token (expires in 12h) Standard cloud credentials
Cloud support AWS only AWS, GCP, Azure, MinIO, R2, Ceph

S3 has no concept of a "registry" — s3lo stores images using the OCI Image Layout format, with each layer as a separate object. Layers are content-addressed by SHA256, so they're never uploaded twice.


How it works in 30 seconds

Your Docker daemon  ──push──►  s3://my-bucket/
                                  blobs/sha256/
                                    a1b2c3d4...  ← layer (shared by all images)
                                    e5f6g7h8...  ← layer
                                  manifests/
                                    myapp/v1.0/
                                      manifest.json

Push: s3lo exports the image from Docker, splits it into content-addressable blobs, and uploads only the blobs that don't already exist in S3.

Pull: s3lo downloads all blobs in parallel and imports the reassembled image into Docker. On EC2 with enhanced networking, this can reach 100 Gbps — limited by the instance, not the registry.

Serve: s3lo starts a lightweight OCI Distribution Spec HTTP server backed by your bucket. Any node with Docker or kubectl can docker pull localhost:5000/myapp:v1.0 directly — no s3lo pull required.

Copy: s3lo pulls directly from any OCI registry (Docker Hub, ECR, GHCR) and uploads to S3 — without going through the local Docker daemon.

TUI: s3lo tui s3://my-bucket/ opens an interactive terminal UI to browse images, inspect tags, view layer sharing across tags, scan for vulnerabilities, and run lifecycle clean — all without leaving the terminal.


Quick install

curl -sSL https://raw.githubusercontent.com/OuFinx/s3lo/main/install.sh | sh
brew install OuFinx/tap/s3lo
go install github.com/OuFinx/s3lo/cmd/s3lo@latest

Supported backends

Scheme Backend Auth
s3:// AWS S3 Standard AWS credentials
s3:// + --endpoint MinIO / Cloudflare R2 / Ceph AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY
gs:// Google Cloud Storage Application Default Credentials
az:// Azure Blob Storage DefaultAzureCredential + AZURE_STORAGE_ACCOUNT
local:// Local filesystem None

Local storage

No cloud account? Use local:// to store images on your filesystem:

s3lo init --local ./local-s3
s3lo push myapp:v1.0 local://./local-s3/myapp:v1.0
s3lo pull local://./local-s3/myapp:v1.0

Local storage uses the same OCI layout as all other backends. All commands work identically across schemes.


Next steps