~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
77 lines · 2.7 KB Raw History
# @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.