Skip to content

madventurescz/discobucket

Repository files navigation

Discobucket

Discobucket is a lightweight S3-compatible proxy backed by the local filesystem. Buckets are directories, object keys map to file paths, and object bytes always remain on disk. Metadata is stored either in .s3meta sidecars or in SQL through JetBrains Exposed.

Requirements

  • JDK 25 for JVM builds and local execution
  • GraalVM 25 for nativeCompile and nativeTest
  • Bun for the bundled React UI build

The Gradle wrapper downloads Gradle automatically.

Quick Start

./gradlew build
./gradlew bootRun

Discobucket listens on port 8080 and stores data under /tmp/discobucket by default. Override the storage root with DISCOBUCKET_ROOT:

DISCOBUCKET_ROOT=/data/my-buckets ./gradlew bootRun

The packaged default allows anonymous S3 requests when no credentials are configured. Point a path-style S3 client at the server:

aws s3 --endpoint-url http://localhost:8080 mb s3://my-bucket
aws s3 --endpoint-url http://localhost:8080 cp file.txt s3://my-bucket/file.txt
aws s3 --endpoint-url http://localhost:8080 ls s3://my-bucket

The bundled bucket browser is available at http://localhost:8080/ui/. It signs requests in the browser with the supplied access key and secret key; credentials are kept in browser session storage.

Supported Operations

Operation Method Path
ListBuckets GET /
CreateBucket PUT /{bucket}
HeadBucket HEAD /{bucket}
DeleteBucket DELETE /{bucket}
GetBucketLocation GET /{bucket}?location
ListObjectsV2 GET /{bucket}?list-type=2
ListObjects (v1) GET /{bucket}
PutObject PUT /{bucket}/{key}
GetObject GET /{bucket}/{key}
HeadObject HEAD /{bucket}/{key}
DeleteObject DELETE /{bucket}/{key}
DeleteObjects POST /{bucket}?delete
CopyObject PUT /{bucket}/{key} with x-amz-copy-source
CreateMultipartUpload POST /{bucket}/{key}?uploads
UploadPart PUT /{bucket}/{key}?partNumber=&uploadId=
CompleteMultipartUpload POST /{bucket}/{key}?uploadId=
AbortMultipartUpload DELETE /{bucket}/{key}?uploadId=
ListMultipartUploads GET /{bucket}?uploads
ListParts GET /{bucket}/{key}?uploadId=

Bucket-level mappings accept both /{bucket} and /{bucket}/. Object operations use path-style addressing only.

Filesystem Layout

/tmp/discobucket/
  my-bucket/
    photos/
      cat.jpg
      cat.jpg.s3meta
    readme.txt
    readme.txt.s3meta
  .multipart/
    {uploadId}/
      upload.json
      part.00001
      part.00001.meta.json
  • Bucket creation dates come from the bucket directory modification time.
  • Object ETags are quoted MD5 digests. Completed multipart objects use a quoted whole-object MD5 followed by the part count.
  • .s3meta files are used only by the filesystem metadata backend and are hidden from listings.
  • Multipart bytes always stage under .multipart/. Filesystem metadata mode also writes upload.json and part metadata sidecars there.
  • Keys containing .. and reserved metadata names are rejected.

Configuration

Property Environment variable Packaged default Description
discobucket.root-path DISCOBUCKET_ROOT /tmp/discobucket Base directory for buckets and multipart staging
discobucket.allow-anonymous DISCOBUCKET_ALLOW_ANONYMOUS true Allow requests when no credentials are configured
discobucket.metadata.backend DISCOBUCKET_METADATA_BACKEND filesystem filesystem or database
discobucket.credentials[N].access-key - - SigV4 access key ID
discobucket.credentials[N].secret-key - - SigV4 secret key
discobucket.credentials[N].buckets.<name> - - Per-bucket permission
discobucket.credentials[N].default-permission - - Permission for buckets not explicitly listed

Virtual threads are enabled through spring.threads.virtual.enabled=true. Actuator exposes only /actuator/health and /actuator/info; health details use Spring Boot's when-authorized policy.

Authentication

Discobucket supports SigV4 authorization headers, presigned URLs, and AWS streaming chunk signatures.

  • With one or more configured credentials, S3 requests must have a valid SigV4 header or presigned query parameters.
  • With no configured credentials, requests are accepted only when discobucket.allow-anonymous=true.
  • /ui/** is excluded from the S3 authentication and bucket-authorization filters so the UI shell can load. Its API calls are still signed by the browser client.
  • Header-signed requests must be within 15 minutes of server time. Presigned URLs are checked for start time, expiration, credential date, and signature.

Example credentials:

discobucket:
  allow-anonymous: false
  credentials:
    - access-key: ADMIN_KEY
      secret-key: ADMIN_SECRET
    - access-key: READER_KEY
      secret-key: READER_SECRET
      buckets:
        shared-data: READ_ONLY
      default-permission: READ_ONLY
Permission Access
READ_ONLY GET and HEAD
READ_WRITE Object reads and writes; bucket creation and deletion remain denied
FULL_ACCESS All operations, including bucket creation and deletion

A credential with no bucket map and no default permission has unrestricted access. Restricted credentials see only accessible buckets in ListBuckets; copy operations also require access to the source bucket.

Database Metadata

Enable database storage for both object and multipart metadata:

discobucket:
  metadata:
    backend: database

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/discobucket
    username: discobucket
    password: secret

PostgreSQL and SQLite JDBC drivers are bundled. H2 is a test dependency. The object_metadata, multipart_upload, and multipart_part tables are created on startup. Object bodies and multipart part bytes remain on disk in database mode. See docs/multipart.md for the multipart design and limitations.

Management UI

The React UI under /ui/ connects to one bucket at a time and supports:

  • prefix and folder navigation
  • object listing and client-side filtering
  • uploads and downloads
  • empty folder-marker creation
  • object deletion
  • paginated loading through ListObjectsV2

The access key, secret key, bucket, and region are stored in sessionStorage, not persistent browser storage. The UI requires credentials even when anonymous server access is enabled.

Scope

Discobucket intentionally does not implement full S3 semantics. Unsupported areas include virtual-hosted addressing, bucket policies and ACLs, versioning, lifecycle rules, encryption, tagging, retention, object lock, replication, notifications, and website hosting. Custom x-amz-meta-* request headers are not currently captured by PutObject.

Multipart listing pagination and several AWS multipart validation rules are also not implemented; see docs/multipart.md.

Build and Test

./gradlew build           # frontend, JVM build, and tests
./gradlew test            # all JVM tests
./gradlew bootRun         # local server
./gradlew nativeCompile   # GraalVM native executable
./gradlew nativeTest      # native tests
./gradlew bootBuildImage  # OCI image; requires Docker
./gradlew docs            # Dokka output in build/dokka/html/

The suite currently contains 93 JUnit tests. It covers MockMvc, the AWS SDK v2, MinIO, simple-s3-client, SigV4 and presigned URLs, bucket authorization, protocol edge cases, both metadata backends, and multipart listing.

CI runs ./gradlew test for pull requests to main. Pushes to main publish latest and commit-SHA container tags to GHCR. Tags matching v* build release artifacts and versioned container images.

Technology

  • Kotlin 2.3.10 and Java 25
  • Spring Boot 4.0.3 with Spring WebMVC and virtual threads
  • Jackson 3.x
  • JetBrains Exposed 1.2.0
  • SpringDoc OpenAPI 3.0.2 at /swagger-ui.html
  • React, TypeScript, Vite, and Bun for the bundled UI
  • GraalVM Native Image support

License

Copyright 2025 MadVentures. Licensed under the Apache License 2.0. See LICENSE.

Releases

Packages

Contributors

Languages