serve¶
Start an HTTP server that speaks the OCI Distribution Spec, serving images stored in the given bucket.
Enables docker pull, kubectl, and any OCI client to pull images directly from S3 — without running s3lo pull first.
Arguments¶
| Argument | Description |
|---|---|
<s3-ref> |
Bucket reference: s3://bucket/, gs://bucket/, az://container/, local://path/ |
Flags¶
| Flag | Default | Description |
|---|---|---|
--port |
5000 |
Port to listen on (0 picks a free port and prints it) |
--host |
127.0.0.1 |
Bind address. Anything other than loopback requires --allow-anonymous |
--allow-anonymous |
false |
Confirms that unauthenticated access is intended. Required for a non-loopback --host |
--tls-cert |
TLS certificate file (enables HTTPS; requires --tls-key) |
|
--tls-key |
TLS key file (requires --tls-cert) |
|
--verify-key |
Verification key (.pub, awskms://, hashivault://): serve only images signed by it |
|
--presign-ttl |
15m |
TTL for S3 presigned blob URLs (max 168h, the SigV4 limit) |
--cache-entries |
1000 |
Manifests to keep cached in memory (0 = unlimited) |
--cache-ttl |
5m |
How long a cached manifest stays valid |
--max-concurrent |
64 |
Maximum concurrent object-storage operations (0 = unlimited) |
What it does¶
Implements the following OCI Distribution Spec endpoints:
| Method | Path | Action |
|---|---|---|
GET |
/v2/ |
OCI version check (200 OK) |
HEAD/GET |
/v2/<name>/manifests/<ref> |
Fetch manifest by tag or digest |
HEAD/GET |
/v2/<name>/blobs/<digest> |
Serve blob (presigned redirect or stream) |
Manifest lookup: <ref> can be a tag (e.g. v1.0) or a digest (e.g. sha256:abc123...). The Docker-Content-Digest header is always set so clients can verify integrity.
Blob serving strategy:
- S3 / S3-compatible: Blobs are served via 303 redirect to a presigned GET URL. No blob data passes through the server.
- GCS, Azure, local: Blobs are streamed from the backend through the server.
Examples¶
# Serve from S3, listen on localhost:5000
s3lo serve s3://my-bucket/ --port 5000
# Pull from it with Docker
docker pull localhost:5000/myapp:v1.0
# Expose on all interfaces (e.g. for remote nodes).
# There is no authentication, so this needs an explicit opt-in AND TLS,
# and should still be fenced off with a firewall or security group.
s3lo serve s3://my-bucket/ --host 0.0.0.0 --port 5000 --allow-anonymous \
--tls-cert cert.pem --tls-key key.pem
# Refuse to serve any image that is not signed by this key
s3lo serve s3://my-bucket/ --verify-key cosign.pub
# MinIO / S3-compatible endpoint
s3lo serve s3://my-bucket/ --endpoint http://minio:9000
# GCS bucket
s3lo serve gs://my-gcs-bucket/
Output¶
Serving s3://my-bucket/ at http://127.0.0.1:5000
Blob strategy: presigned URLs (S3)
Press Ctrl+C to stop.
For non-S3 backends:
Serving gs://my-gcs-bucket/ at http://127.0.0.1:5000
Blob strategy: streaming (GCS)
Press Ctrl+C to stop.
Notes¶
- The server does not implement authentication: anyone who can reach the port can read every image in the bucket, including the presigned URLs it hands out for blobs. It binds
127.0.0.1by default, and a non-loopback--hostis refused unless--allow-anonymousis passed. For production use, put it behind an authenticating reverse proxy (nginx, Caddy, etc.), serve it over TLS, and restrict access with firewall or security-group rules. --verify-keymakes the server refuse any manifest without a valid signature from that key, which is whats3lo signexists for. A pull by bare digest is only served once the same content has been verified through its tag in this process.- Every request is written to the access log (method, path, status, bytes, client address). Query strings and headers are never logged, so presigned URL signatures do not end up on disk.
- For large images on GCS, Azure, or local backends,
s3lo pullis more efficient — the streaming path loads the entire blob into memory before forwarding. - The
--presign-ttlflag controls how long presigned S3 URLs remain valid. Increase it if clients are slow to start downloading after receiving the redirect. Values above168hare rejected: SigV4 will not sign for longer, and S3 would refuse the resulting URLs. - Repository names and tags are validated against the OCI grammar before dispatch. A malformed name or reference is answered with
400 NAME_INVALID/TAG_INVALID/DIGEST_INVALIDrather than being passed through to storage.