Overview
The GitAssets S3 Worker is a standalone Cloudflare Worker that provides an S3-compatible REST API on top of GitHub repositories. It translates standard S3 operations into GitHub API calls and serves files via jsDelivr CDN.
Deploy
The S3 worker lives in the worker-s3/ directory. No secrets needed — authentication comes from the S3 request itself.
1. Clone the repo
git clone https://github.com/danielsebesta/git-assets.git
cd git-assets/worker-s3
2. Configure your domain optional
Edit wrangler.toml to set your custom domain:
routes = [
{ pattern = "s3.yourdomain.com", zone_name = "yourdomain.com", custom_domain = true }
]
Your domain must be added as a zone in Cloudflare. Wrangler creates the DNS record automatically.
3. Deploy
npx wrangler deploy
That's it. Your S3 endpoint is live at your workers.dev subdomain or custom domain.
Authentication
Two authentication methods are supported. Use whichever fits your setup.
Bearer token simple
Pass your credentials in the Authorization header as owner/repo:github_token:
Authorization: Bearer owner/repo:ghp_xxxxxxxxxxxx
AWS Signature V4 s3-compatible
For use with standard S3 SDKs and CLIs. Map your credentials like this:
| S3 credential | GitAssets value | Example |
|---|---|---|
| Access Key ID | owner/repo | danielsebesta/my-assets |
| Secret Access Key | any value | unused |
| Security Token | GitHub token | ghp_xxxxxxxxxxxx |
The GitHub token is passed via the x-amz-security-token header. The secret access key is not validated — set it to any non-empty string.
GitHub token permissions
Create a fine-grained personal access token with:
- Repository access: select the target repo
- Contents: Read and write
Operations
URL format: https://your-worker/{bucket}/{key} where bucket is the branch name and key is the file path.
Upload or overwrite a file. Automatically detects if the file exists and updates it.
PUT /{branch}/{path/to/file}
Returns ETag (blob SHA) and x-amz-version-id (commit SHA) in response headers.
Returns a 302 redirect to the jsDelivr CDN URL for the file.
GET /{branch}/{path/to/file}
Returns file metadata: ETag, Content-Length, Content-Type.
HEAD /{branch}/{path/to/file}
Delete a file from the repository.
DELETE /{branch}/{path/to/file}
List files and directories. Returns S3-compatible XML.
GET /{branch}?list-type=2&prefix=images/&delimiter=/
Supports prefix, delimiter, and max-keys parameters.
Copy a file within the repo. Set the source via the x-amz-copy-source header.
PUT /{branch}/{new/path}
x-amz-copy-source: /{branch}/{old/path}
Examples
cURL
# Upload a file
curl -X PUT \
-H "Authorization: Bearer user/repo:ghp_token" \
--data-binary @photo.jpg \
https://your-worker.dev/main/images/photo.jpg
# Download (follows redirect to CDN)
curl -L \
-H "Authorization: Bearer user/repo:ghp_token" \
https://your-worker.dev/main/images/photo.jpg
# Delete
curl -X DELETE \
-H "Authorization: Bearer user/repo:ghp_token" \
https://your-worker.dev/main/images/photo.jpg
# List files in a directory
curl -H "Authorization: Bearer user/repo:ghp_token" \
"https://your-worker.dev/main?list-type=2&prefix=images/"
AWS CLI
# Configure the CLI
export AWS_ACCESS_KEY_ID="owner/repo"
export AWS_SECRET_ACCESS_KEY="unused"
export AWS_SESSION_TOKEN="ghp_xxxxxxxxxxxx"
export AWS_DEFAULT_REGION="auto"
# Upload
aws s3 cp photo.jpg s3://main/images/photo.jpg \
--endpoint-url https://your-worker.dev
# List
aws s3 ls s3://main/images/ \
--endpoint-url https://your-worker.dev
JavaScript (fetch)
const endpoint = 'https://your-worker.dev';
const auth = 'Bearer owner/repo:ghp_xxxxxxxxxxxx';
// Upload
await fetch(`${endpoint}/main/images/logo.png`, {
method: 'PUT',
headers: { Authorization: auth },
body: fileBlob,
});
// Get CDN URL (follow redirect)
const res = await fetch(`${endpoint}/main/images/logo.png`, {
headers: { Authorization: auth },
redirect: 'manual',
});
const cdnUrl = res.headers.get('Location');
Python (boto3)
import boto3
s3 = boto3.client('s3',
endpoint_url='https://your-worker.dev',
aws_access_key_id='owner/repo',
aws_secret_access_key='unused',
aws_session_token='ghp_xxxxxxxxxxxx',
region_name='auto',
)
# Upload
s3.upload_file('photo.jpg', 'main', 'images/photo.jpg')
# List
response = s3.list_objects_v2(Bucket='main', Prefix='images/')
Limits
| Constraint | Limit | Source |
|---|---|---|
| Max file size | 100 MB | GitHub API |
| API rate limit | 5,000 req/hour | GitHub (authenticated) |
| Recommended repo size | 1 – 5 GB | GitHub guidelines |
| Worker request size | 100 MB | Cloudflare Workers |
| Worker execution time | 30 seconds | Cloudflare (free plan) |
GetObject returns a 302 redirect to jsDelivr CDN, so file downloads don't count against Worker or GitHub limits.