Bucketrb

Bucketrb is a small local S3-compatible server for Ruby development and test environments. It is intentionally narrower than MinIO or LocalStack: it focuses on the S3 API surface commonly used by Rails apps, aws-sdk-s3, ActiveStorage adjacent integrations, and browser direct uploads.

Status

This project is an early MVP. It currently implements:

  • path-style S3 requests
  • unsigned and presigned GET, HEAD, PUT, POST, and DELETE
  • CreateBucket and HeadBucket
  • GetObject, HeadObject, PutObject, CopyObject, and DeleteObject
  • ListObjects and ListObjectsV2
  • DeleteObjects
  • browser-style presigned POST multipart uploads
  • object metadata through x-amz-meta-*
  • filesystem persistence
  • permissive CORS for local browser uploads

It does not currently implement S3 authorization, bucket policies, ACL semantics, multipart upload APIs, object versioning, lifecycle rules, virtual host-style bucket addressing, or service APIs outside S3.

See ROADMAP.md for planned work and explicit non-goals.

Install

From a local checkout:

bundle install
bundle exec exe/bucketrb serve --root ./data --bucket my-bucket

After release:

gem install bucketrb
bucketrb serve --root ./data --bucket my-bucket

CLI

bucketrb serve \
  --host 127.0.0.1 \
  --port 4566 \
  --root ./data/bucketrb \
  --bucket app-dev-bucket

Options:

  • --host HOST: bind host. Default: 127.0.0.1
  • --port PORT: bind port. Default: 4566
  • --root PATH: filesystem storage root. Default: data/bucketrb
  • --bucket NAME: create a bucket at startup. May be repeated.
  • --buckets-from-env: create buckets from environment variables ending in _BUCKET or _S3_BUCKET

Ruby SDK Example

require "aws-sdk-s3"

client = Aws::S3::Client.new(
  endpoint: "http://127.0.0.1:4566",
  region: "ap-northeast-1",
  access_key_id: "bucketrb",
  secret_access_key: "bucketrb",
  force_path_style: true
)

client.create_bucket(bucket: "example")
client.put_object(bucket: "example", key: "hello.txt", body: "hello")
puts client.get_object(bucket: "example", key: "hello.txt").body.read

Docker

Build locally:

docker build -t bucketrb .
docker run --rm -p 4566:4566 -v "$PWD/data:/data" \
  -e APP_S3_BUCKET=app-dev-bucket \
  bucketrb

Migration Sketch

An application that currently uses a local S3 emulator can run Bucketrb on the same local port and point its S3 SDK configuration at that endpoint:

services:
  bucketrb:
    image: bucketrb:latest
    ports:
      - 127.0.0.1:${S3_EMULATOR_PORT:-4566}:4566
    environment:
      - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-ap-northeast-1}
      - APP_S3_BUCKET=${APP_S3_BUCKET:-app-dev-bucket}
      - PUBLIC_ASSETS_BUCKET=${PUBLIC_ASSETS_BUCKET:-public-assets-dev}
      - PRIVATE_FILES_BUCKET=${PRIVATE_FILES_BUCKET:-private-files-dev}
    volumes:
      - ./data/bucketrb:/data:delegate

For aws-sdk-s3, use path-style addressing:

endpoint: ENV.fetch("S3_ENDPOINT", "http://127.0.0.1:4566")
force_path_style: true

Test

ruby -Ilib:test test/bucketrb/object_store_test.rb
ruby -Ilib:test test/bucketrb/s3_contract_test.rb

The contract test starts a localhost HTTP server and exercises it through aws-sdk-s3.