Connecting clients
Object Storage speaks the standard AWS S3 protocol. Every official SDK, every CLI that knows about S3 — aws, aws s3api, boto3, @aws-sdk/client-s3, rclone, mc (MinIO Client), s3cmd, the AWS SDKs for Go / Java / PHP / Ruby / .NET — works against Runsite by changing only the endpoint URL.
You always need the same four values:
endpoint | https://s3.runsite.app |
region | auto |
access_key_id | from the dashboard, starts with rs_ak_stg_ |
secret_access_key | from the dashboard, starts with rs_sk_stg_ |
Path-style addressing is what the dashboard examples use — set forcePathStyle: true (Node) or addressing_style: "path" (Python). Virtual-hosted style works too.
Python — boto3
Section titled “Python — boto3”import osimport boto3from botocore.config import Config
s3 = boto3.client( "s3", endpoint_url="https://s3.runsite.app", aws_access_key_id=os.environ["S3_ACCESS_KEY_ID"], aws_secret_access_key=os.environ["S3_SECRET_ACCESS_KEY"], region_name="auto", config=Config(s3={"addressing_style": "path"}),)
s3.upload_file("local.png", "user-uploads", "avatars/u123.png")
obj = s3.get_object(Bucket="user-uploads", Key="avatars/u123.png")data = obj["Body"].read()
s3.delete_object(Bucket="user-uploads", Key="avatars/u123.png")boto3 automatically uses multipart uploads for files larger than 8 MB when you call upload_file or upload_fileobj.
Node.js — @aws-sdk/client-s3
Section titled “Node.js — @aws-sdk/client-s3”import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand,} from '@aws-sdk/client-s3';
const s3 = new S3Client({ endpoint: 'https://s3.runsite.app', region: 'auto', credentials: { accessKeyId: process.env.S3_ACCESS_KEY_ID, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, }, forcePathStyle: true,});
await s3.send( new PutObjectCommand({ Bucket: 'user-uploads', Key: 'avatars/u123.png', Body: fileBuffer, ContentType: 'image/png', }),);
const get = await s3.send( new GetObjectCommand({ Bucket: 'user-uploads', Key: 'avatars/u123.png' }),);const body = await get.Body.transformToByteArray();For multipart uploads of large files, use Upload from @aws-sdk/lib-storage:
import { Upload } from '@aws-sdk/lib-storage';
await new Upload({ client: s3, params: { Bucket: 'user-uploads', Key: 'video.mp4', Body: stream, ContentType: 'video/mp4', },}).done();AWS CLI
Section titled “AWS CLI”Configure once with a named profile:
aws configure --profile runsite# AWS Access Key ID: rs_ak_stg_…# AWS Secret Access Key: rs_sk_stg_…# Default region: auto# Default output: jsonThen talk to Runsite by passing --endpoint-url:
aws --profile runsite --endpoint-url https://s3.runsite.app \ s3 ls
aws --profile runsite --endpoint-url https://s3.runsite.app \ s3 cp ./report.pdf s3://user-uploads/reports/2026-05.pdf
aws --profile runsite --endpoint-url https://s3.runsite.app \ s3 sync ./build s3://static-assets/build/To avoid passing --endpoint-url every time, set it in ~/.aws/config:
[profile runsite]region = autoendpoint_url = https://s3.runsite.apps3 = addressing_style = pathrclone
Section titled “rclone”rclone is the easiest tool for backups, sync jobs and one-off uploads of large directories.
rclone config create runsite s3 \ provider Other \ access_key_id rs_ak_stg_… \ secret_access_key rs_sk_stg_… \ endpoint https://s3.runsite.app \ region auto \ force_path_style trueThen:
rclone ls runsite:user-uploadsrclone copy ./photos runsite:user-uploads/photos -Prclone sync ./build runsite:static-assets/build --deleteMinIO Client (mc)
Section titled “MinIO Client (mc)”mc alias set runsite https://s3.runsite.app rs_ak_stg_… rs_sk_stg_…
mc ls runsite/mc cp ./report.pdf runsite/user-uploads/reports/mc mirror ./build runsite/static-assets/buildGo — aws-sdk-go-v2
Section titled “Go — aws-sdk-go-v2”import ( "context" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3")
cfg, _ := config.LoadDefaultConfig(context.TODO(), config.WithRegion("auto"), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider( os.Getenv("S3_ACCESS_KEY_ID"), os.Getenv("S3_SECRET_ACCESS_KEY"), "", )),)
client := s3.NewFromConfig(cfg, func(o *s3.Options) { o.BaseEndpoint = aws.String("https://s3.runsite.app") o.UsePathStyle = true})
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ Bucket: aws.String("user-uploads"), Key: aws.String("hello.txt"), Body: strings.NewReader("Hello, Runsite"),})PHP — AWS SDK for PHP
Section titled “PHP — AWS SDK for PHP”use Aws\S3\S3Client;
$s3 = new S3Client([ 'version' => 'latest', 'region' => 'auto', 'endpoint' => 'https://s3.runsite.app', 'use_path_style_endpoint' => true, 'credentials' => [ 'key' => getenv('S3_ACCESS_KEY_ID'), 'secret' => getenv('S3_SECRET_ACCESS_KEY'), ],]);
$s3->putObject([ 'Bucket' => 'user-uploads', 'Key' => 'avatars/u123.png', 'SourceFile' => '/tmp/u123.png',]);Reading from a public bucket
Section titled “Reading from a public bucket”If the bucket is public, anyone can read objects directly over HTTPS — no SDK, no signing, no credentials:
https://s3.runsite.app/<bucket>/<key>For example:
https://s3.runsite.app/marketing-assets/hero.pngThe same object is also reachable via virtual-hosted style:
https://marketing-assets.s3.runsite.app/hero.pngThis is what you embed in <img> tags, <a href>, CSS background-image, etc.
Bucket-scoped keys
Section titled “Bucket-scoped keys”A per-bucket key still talks to the same endpoint — only the requests it can authenticate are restricted to that one bucket. From the SDK’s point of view nothing changes; trying to call ListBuckets or touch another bucket simply returns 403 Forbidden.
Wiring it into a Web Service
Add the four values (endpoint, region, access key, secret) as environment variables on your Runsite Web Service under Settings → Environment, then read them in your code with process.env.S3_… / os.environ["S3_…"]. Pick Save & Deploy so the new container picks them up.