Skip to content

Bring your own S3

On most Git hosts the provider stores your Git objects, so you do not really own the data. Leaving means an export and a migration. Many of those hosts also train AI models on repository contents. Amendable does not. You still clone and push against Amendable, but Git objects can live in Amendable’s S3 bucket or in an Amazon S3 bucket you own (bring-your-own storage, Pro). Storage overage is $0.25 per GiB-month in your bucket versus $0.50 on managed storage. See Usage and quotas.

Amendable keeps caches on its servers so clones stay fast. The durable copy is in the bucket. You do not have to trust us with the only copy: keep it in a bucket you own. Revoke the IAM role or deny S3 access and Amendable cannot read or write those objects. You can cut access at any time. A warm node may still serve a clone from cache after you cut access, until that cache goes cold. Cold nodes talk to the bucket again. Because the objects are in your bucket, you can also run your own pipeline on them: S3 notifications, inventory, replication, or webhooks when Git refs move. That is real data ownership.

An open source tool is coming soon that converts those layer tarballs into a normal .git directory on your machine. You will not need Amendable to reconstruct Git from objects you already own.

Git clients clone and push to Amendable. Amendable serves the repository and stores Git layer tarballs in your S3 bucket.

A hosted Git repo is the same kind of folder as .git on your laptop: objects, refs, and a few metadata files. Amendable does not keep that folder on one machine’s disk. Each Git push (and repo create) becomes an immutable layer: a tarball of only what changed. Those tarballs stack. A later clone pulls the chain, and Amendable serves ordinary Git over HTTPS. Your Git client never talks to S3. Amendable does.

Every 10 layers Amendable writes a snapshot: a full Git tree, not a diff. Later mounts start from that snapshot plus the pushes after it, so they do not walk an endless chain.

Stacked Git layers. After three pushes the clone mounts create plus three diffs. After ten layers a snapshot stores a full Git tree, and later clones mount that snapshot plus the diffs after it.

The merge uses Linux overlayfs: older layers are read-only lower directories, the new layer is the writable upper directory (only files this push changed). That upper dir is packed into {prefix}layers/{layer-id}.tar.gz in S3. Same stacking idea as container image layers.

A force-push that rewrites Git history still writes a new layer. Earlier layers stay. Clones serve the current tip. The layer chain is the auditable history: who changed the repository, and the files they wrote, even when git log no longer shows it.

Those tarballs are the repository. Amendable does not keep another durable copy. A cold node reconstructs Git from those objects. A warm node may still clone from cache. Treat the keys as production data. The longer design write-up is How BeanHub works: layer-based Git repos. BeanHub and Amendable share this engine.

UI: Settings → Bring-your-own storage

API (needs API_STORAGE):

Terminal window
curl -sS https://api.amendable.io/v1/storage-buckets/oidc-setup \
-H "Authorization: Bearer $AMENDABLE_TOKEN"

Fields you must use as-is:

Field Example
issuer https://oidc.amendable.io
audience amendable-byo-storage
subject account:<your-user-uuid>
sample_trust_policy IAM trust JSON with the correct hostname

Always copy issuer, audience, and subject from this payload. Do not invent them.

subject is account: plus your user UUID, not your username.

  • Any AWS region
  • Block public access
  • SSE-S3 is enough
  • Prefix, default amendable/
  • Do not use names that look like platform buckets (amendable-repos-*, amendable-download-*)

The AWS console is the easiest path. No extra tools. Use the AWS CLI if you already have aws. Terraform is for when you already manage AWS as code.

Sign in at https://console.aws.amazon.com/. Copy issuer, audience, and subject from the setup payload first. IAM fetches the OIDC thumbprint for you in the console, so you do not paste it.

Open S3 → Create bucket. Name it something like my-amendable-layers. Choose the AWS region where you want the bucket. Keep Block all public access on. Default encryption SSE-S3 is enough.

AWS S3 Create bucket form. Example bucket name my-amendable-layers, an example region US West (Oregon) us-west-2, Block all public access on, SSE-S3 encryption selected, Create bucket.

Open IAM → Identity providers → Add provider. Choose OpenID Connect. Provider URL is https://oidc.amendable.io with no path and no trailing slash. Audience is amendable-byo-storage. Then Add provider.

AWS IAM Add identity provider. OpenID Connect selected, provider URL https://oidc.amendable.io, audience amendable-byo-storage, Add provider.

IAM → Roles → Create role → Web identity. Identity provider oidc.amendable.io, audience amendable-byo-storage. Skip AWS managed policies. Name the role amendable-byo-storage.

AWS IAM Create role. Web identity selected, identity provider oidc.amendable.io, audience amendable-byo-storage, role name amendable-byo-storage.

The wizard only binds aud. Open the role → Trust relationships → Edit trust policy. Add oidc.amendable.io:sub equal to account:<uuid> from the setup payload. Leave the aud line as amendable-byo-storage.

AWS IAM Edit trust policy JSON. AssumeRoleWithWebIdentity for oidc.amendable.io with aud amendable-byo-storage and sub account:YOUR_USER_UUID highlighted.

On the same role, Permissions → Add permissions → Create inline policy → JSON. Allow s3:ListBucket on the bucket with s3:prefix amendable/ and amendable/*, and object Get, Put, Delete, plus multipart, on bucket/amendable/*. Name the policy amendable-byo-objects. Copy the role ARN when you are done.

AWS IAM Create policy JSON tab. Inline policy amendable-byo-objects allows ListBucket on prefix amendable/* and object read write delete multipart on my-amendable-layers/amendable/*.

Minimum object actions: s3:GetObject, s3:PutObject, s3:DeleteObject, s3:AbortMultipartUpload, s3:ListMultipartUploadParts.

Verify writes {prefix}.amendable-canary/{uuid}.txt (Put, Head, Delete) then uses {prefix}layers/{layer-id}.tar.gz for Git.

Terminal window
curl -sS -X POST https://api.amendable.io/v1/storage-buckets \
-H "Authorization: Bearer $AMENDABLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "prod-layers",
"provider": "S3",
"auth_mode": "OIDC",
"bucket_name": "my-amendable-layers",
"region": "eu-west-1",
"key_prefix": "amendable/",
"role_arn": "arn:aws:iam::123456789012:role/amendable-byo-storage",
"is_default": true
}'

Status starts as PENDING_VERIFICATION. Then:

Terminal window
curl -sS -X POST \
https://api.amendable.io/v1/storage-buckets/BUCKET_ID/verify \
-H "Authorization: Bearer $AMENDABLE_TOKEN"

Success: status is ACTIVE and last_validated_at is set. Failure: FAILED, flash or 422, and a row in Error log / GET /v1/storage-buckets/errors.

Pass storage_bucket_id on create, or set the bucket as account default. Binding cannot change later. You cannot delete a bucket that still has repos.

Verify checks assume-role (OIDC) and canary Put/Head/Delete. It does not prove:

  • that layers/*.tar.gz objects still exist
  • that GetObject on layers/ is still allowed

If layer objects are missing or changed, Git on a cold node can 500 (LoadImageError, S3 404, or digest mismatch). A warm node may still clone from cache. The BYO error log may stay empty. Do not tidy {prefix}layers/*.tar.gz. See How Git is stored.

After IAM changes, click Verify again. That is the check that writes a readable error.

GET /v1/storage-buckets/errors and Settings → BYO storage → Error log.

Amendable BYO storage error log in dark theme. Settings sidebar has BYO storage open and Error log selected. A table lists ASSUME_ROLE AccessDenied, VALIDATE AccessDenied on a canary PutObject, and HEAD NoSuchKey for a layers tarball.

Operations you will see: ASSUME_ROLE, VALIDATE, UPLOAD, HEAD, and similar. Retention: 30 days / 500 rows per user.

Common Verify failures:

Symptom Cause
AccessDenied on assume-role Trust iss host, aud, or sub mismatch; bad role ARN
S3 AccessDenied on canary Missing Put/Head/Delete on {prefix}*
422 OIDC required Amazon S3 + static keys
422 bucket name invalid Platform bucket name
403 BYO requires Pro (feature flag)