Axonpack

Any S3-compatible host

What a storage host must support for this package, and a setup checklist for any of them.

The package talks plain S3 over HTTP, with its own small SigV4 signer. It has no code per provider. So any host that supports the requests below should work. AWS S3, Cloudflare R2 and MinIO have their own pages; this one is for everything else.

What the host must support

NeedUsed for
Path-style URLsEvery request goes to <endpoint>/<bucket>/<key>
SigV4, service s3Every request the CLI makes, and the app's reads from a private bucket
GET, HEAD, PUT, DELETE on objectsReading, checking, uploading and removing files
ListObjectsV2preview prune (GET /<bucket>?list-type=2)
Conditional PUTUpdating the two lists safely (If-Match, If-None-Match: *)
Anonymous GETA public bucket only

Path-style URLs

The bucket is the first path segment of the URL: https://<endpoint>/<bucket>. The package never puts the bucket in the host name (virtual-hosted style). A host that only serves virtual-hosted URLs works for a public bucket's reads, since those take any address, but not for publishing or for a private bucket.

SigV4 and the region

Signed requests use AWS Signature Version 4 with the service s3, the region you set, and an x-amz-content-sha256 header holding the body's hash. The host name is part of the signature, so a proxy in front must pass the Host header through unchanged.

The region is PREVIEW_S3_REGION for the CLI and region in the app. Both default to us-east-1. Use whatever region your host expects; many accept us-east-1 or auto.

Conditional PUT

The two lists, index.json and index/<platform>/<key>.json, are read, changed and written back. The write sends If-Match with the ETag it read, or If-None-Match: * when the file is new. If the host answers 412 or 409, someone else wrote first: the CLI reads again and retries, up to 8 times. That is what lets two publishes at the same time both end up listed.

If a host ignores those headers, publishing still works, but two publishes that overlap can lose one of the entries: the later write wins. Files and manifests are not affected. Publishing one at a time, as one CI job per pull request mostly does, avoids it. If an entry goes missing, publish that preview again.

Reads from the app

A public bucket needs anonymous GET on its objects. The app reads <url>/<key> with no signature, so url can be any address the files are public under, such as a CDN.

A private bucket needs a read-only key. The app signs each GET with it, so url must be the real <endpoint>/<bucket> address. Give the key ListBucket as well as GetObject if the host has that distinction: without it, many hosts answer a missing file with 403 instead of 404, and a build with nothing published yet shows an error instead of an empty list.

Missing files are expected: an app asks for its own build's list before anything is published for it. The package treats 404 as "nothing yet", and also 403 on an unsigned read, since a public bucket often answers that way.

CORS is not needed

CORS is a browser rule. The app's requests come from React Native's native networking on Android and iOS, which does not apply it, and the package does not run on the web. The CLI runs in Node, which does not apply it either. So the bucket needs no CORS settings.

Setup checklist

  1. Create a bucket. One bucket per app, or one shared bucket with a folder per app in the URL.
  2. Make the write key. Read, write, delete and list on that bucket only. In S3 policy terms: s3:GetObject, s3:PutObject, s3:DeleteObject and s3:ListBucket.
  3. Choose public or private.
    • Public: allow anonymous read on the bucket's objects, and nothing else.
    • Private: make a read key with read and list only (s3:GetObject, s3:ListBucket).
  4. Set the publish settings in CI secrets or a gitignored .env.local:
    PREVIEW_S3_URL=https://<endpoint>/<bucket>
    PREVIEW_S3_ACCESS_KEY_ID=...
    PREVIEW_S3_SECRET_ACCESS_KEY=...
    PREVIEW_S3_REGION=<the host's region>
  5. Set the app's settings: the bucket's public address, or the same URL plus the read key and region. See Settings and env files.
  6. Test a publish with one platform to keep it quick:
    npx preview publish --id storage-test --platform android
    It prints the URL of the list it wrote. Open it, or curl it for a public bucket: you should see storage-test. Then check the app lists it, and clean up with npx preview unpublish storage-test and npx preview prune.

Other hosts

Not tested with this package

None of the hosts below has been run against this package. Each entry says what its own docs say about the needs above, with links. Their docs are the source of truth for creating buckets, keys and access rules, and for prices.

AWS S3, Cloudflare R2, MinIO and MinIO AIStor have their own pages: AWS S3, Cloudflare R2, MinIO, MinIO AIStor.

Where a host's docs don't say it honours conditional PUT, assume it doesn't: publish one preview at a time, as described above.

Garage (self-hosted)

  • Licence: AGPL v3 (introduction).
  • Settings: PREVIEW_S3_URL=http(s)://<host>:3900/<bucket>. Path-style is always on. Sign with the s3_region from Garage's config (garage in its sample), not us-east-1 (configuration).
  • Buckets and keys: quick start.
  • Public reads: Garage serves public buckets as websites on a separate endpoint (exposing websites). Anonymous reads on the S3 API are not documented, so a read key is the documented path.
  • Conditional PUT: not documented. The S3 compatibility page marks PutObject as implemented, with no word on If-Match or If-None-Match.

SeaweedFS (self-hosted)

  • Licence: Apache 2.0 (repository).
  • Settings: PREVIEW_S3_URL=http(s)://<host>:8333/<bucket>, the port in its examples. Its AWS CLI example signs with us-east-1 (AWS CLI with SeaweedFS).
  • Keys: actions can be limited to one bucket (S3 credentials).
  • Public reads: a bucket policy (S3 bucket policies). Check S3 configuration too: without configured credentials it allows open access.
  • Conditional PUT: documented. Its conditional operations page says a read-modify-write cycle "that fails with 412 means another writer changed the object", and that with If-None-Match: "*" the losing concurrent PUTs "return 412 Precondition Failed".

Ceph RADOS Gateway (self-hosted)

  • Licence: see the Ceph docs.
  • Settings: PREVIEW_S3_URL=https://<gateway>/<bucket>. Path-style is the form its bucket operations use. The signing region is not documented; use the zonegroup's API name if you set one.
  • Users and keys: radosgw-admin, limited per bucket with bucket policies.
  • Public reads: a public-read ACL on the bucket (bucket operations page) or a bucket policy.
  • Conditional PUT: not documented. The object operations page lists if-match and if-none-match only for GET, not for PUT.

Backblaze B2 (hosted)

  • Pricing: B2 pricing.
  • Settings: PREVIEW_S3_URL=https://s3.<region>.backblazeb2.com/<bucket>, HTTPS only (calling the S3 API). The region is the part of the endpoint between s3. and .backblazeb2.com.
  • Keys: application keys can be limited to one bucket, read and write or read only (application keys).
  • Public reads: a bucket set to public (buckets).
  • Conditional PUT: not documented. The PutObject reference lists neither If-Match nor If-None-Match.

Wasabi (hosted)

DigitalOcean Spaces (hosted)

  • Pricing: Spaces pricing.
  • Settings: PREVIEW_S3_URL=https://<region>.digitaloceanspaces.com/<bucket>. Path-style is supported (limits). Sign with us-east-1; the endpoint picks the datacenter (AWS SDKs).
  • Keys: per-bucket keys, read or read/write/delete (manage access).
  • Public reads: files are private unless made public one by one (file permissions). The package uploads with no ACL, so plan on a private bucket with a read key.
  • Conditional PUT: not documented, on the S3 compatibility page or the limits page.

Google Cloud Storage, XML API (hosted)

  • Pricing: Cloud Storage pricing.
  • Settings: PREVIEW_S3_URL=https://storage.googleapis.com/<bucket> (request endpoints), with HMAC keys of a service account. Sign with the bucket's location (signatures).
  • Keys: bucket-level IAM roles on the service account (IAM permissions).
  • Public reads: Storage Object Viewer for allUsers on the bucket (making data public).
  • Conditional PUT: not with the headers this package sends. The request preconditions page says If-Match and If-None-Match are "applicable for requests that retrieve data", and uses x-goog-if-generation-match for writes, which the package does not send. So treat the lists as unprotected and publish one at a time.

Next: Settings and env files

On this page