chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
# @pdsjs/oci
Host OCI container images natively in an atproto account. Tags, manifests
and layers live in the account's PDS as a `dev.pdsjs.oci.repo` record plus
blob storage, so an image rides along in CAR backups, migrates with the
account, and needs no separate registry service:
```sh
docker login pds.example.com -u alice.example.com
docker push pds.example.com/my-app:v1
docker pull pds.example.com/my-app:v1 # anonymous
```
## How it works
- An OCI digest is `sha256:<hex>`; an atproto blob CID is CIDv1 + raw codec
+ a sha256 multihash. Both hold the same 32 hash bytes, so a layer is one
blob and a blob fetch is a digest re-encoding plus `getBlob`. No mapping
table exists.
- Manifest bytes are stored as blobs exactly as pushed, because a digest
covers the exact serialization. The record carries each manifest's media
type so pulls are served with the right `Content-Type`.
- One record per image repository (collection `dev.pdsjs.oci.repo`, rkey =
name with `/` encoded as `:`). It holds the tag list and a blob reference
for every stored manifest, config and layer, which keeps those blobs out
of the PDS's orphan cleanup. Deleting a manifest drops the references and
the orphan sweep frees the bytes.
- Layer uploads stream through the blob writer with an incremental hash, so
a layer is never held in memory whole. Monolithic and chunked uploads,
cross-repository mounts, ranged blob reads and multi-arch indexes all
work.
## Authentication
Pulls are anonymous. Pushes authenticate with the account password or an
app password, sent the way `docker login` sends them; the username is the
account's handle or DID. The server issues Bearer tokens from
`/v2/token` — the flow docker requires for anonymous pulls — and also
accepts Basic credentials directly, so `curl -u` works.
## Enabling
Node:
```js
const server = await createServer({
// ...
experimental: { oci: true },
});
```
Cloudflare: set `PDS_EXPERIMENTAL_OCI = "true"`.
Both mount `createOciExtension()` through core's `extensions` option. A
platform that builds `PersonalDataServer` itself does the same:
```js
import { createOciExtension } from '@pdsjs/oci/extension';
new PersonalDataServer({
// ...
extensions: [createOciExtension({ maxBlobSize: 512 * 1024 * 1024 })],
});
```
The registry serves under `/v2/` on the PDS hostname. Docker requires TLS
for any registry that is not `localhost`; a PDS behind HTTPS already
satisfies that.
## Limits
- Layers stream, but each layer arrives in one request unless the client
chunks. A platform's per-request body cap (100 MB on the lowest
Cloudflare plan) bounds the largest layer such a deployment accepts.
- The referrers API is not served; clients fall back to the tag schema.
- `_catalog` lists the first hundred repositories.