Blog

Using RustFS as Cloud Storage in Serverpod

A comprehensive guide on configuring and using RustFS, an S3-compatible storage backend, with Serverpod for efficient file management.

Posted on: 2026-03-03 by AI Assistant


Serverpod provides robust built-in mechanisms for handling file uploads and storage, but sometimes you need more scalable or customized solutions. Enter RustFS, an S3-compatible storage backend. Since RustFS implements the S3 API, Serverpod can communicate with it using the exact same mechanisms as AWS S3, making it a powerful and flexible alternative.

In this guide, we’ll walk through how to configure RustFS as a storage backend for your Serverpod project, handle uploads, and retrieve files.

Prerequisites

Before writing any Dart code, you need to prepare your RustFS environment:

  1. Set up a RustFS instance.
  2. Create a bucket.
  3. Generate an access key and a secret key.

Tip: If you plan to expose files publicly, it is highly recommended to run RustFS behind a reverse proxy or CDN (such as Nginx or Cloudflare). This simplifies TLS/SSL certificate management and allows you to use a custom domain.

1. Credentials Setup

After generating your access and secret keys in RustFS, you need to add them to your Serverpod configuration. Serverpod uses AWS-style credential names for all S3-compatible storage providers.

You can add them to your passwords.yaml file:

shared:
  RustFSAccessKeyId: 'RUSTFS_ACCESS_KEY'
  RustFSSecretKey: 'RUSTFS_SECRET_KEY'

Alternatively, you can provide them as environment variables:

SERVERPOD_RUSTFS_ACCESS_KEY_ID=RUSTFS_ACCESS_KEY
SERVERPOD_RUSTFS_SECRET_KEY=RUSTFS_SECRET_KEY

2. Adding the RustFS Client Package

Next, include the RustFS cloud storage integration package in your pubspec.yaml file.

dependencies:
  serverpod_cloud_storage_rustfs:
    git:
      url: https://github.com/anoochit/serverpod_cloud_storage_rustfs.git

Then, import it into your server.dart file:

import 'package:serverpod_cloud_storage_rustfs/serverpod_cloud_storage_rustfs.dart' as rustfs;

3. Configuring Cloud Storage

After creating your Serverpod instance in server.dart, add the cloud storage configuration before starting the pod. You can replace the default public or private storage by setting the storageId accordingly.

When using RustFS, you configure it similarly to S3, but point the host to your RustFS endpoint. If RustFS is exposed via a custom domain, set the publicHost to that domain.

pod.addCloudStorage(
  rustfs.RustFSCloudStorage(
    serverpod: pod,
    storageId: 'public', // Set to 'private' for private storage
    public: true,
    region: 'us-west-2', // Region is not strictly validated for RustFS
    bucket: 'mybucket',
    host: 'd0beb585a210.ngrok-free.app', // Your RustFS endpoint
    publicHost: 'd0beb585a210.ngrok-free.app', // Your Public host (e.g., CDN/Proxy domain)
  ),
);

Note: For S3-compatible services like RustFS, the region value is not strictly validated and can be set to any valid AWS region string.

4. Uploading Files

By default, Serverpod provides database-backed public and private file storage. Once RustFS is configured via RustFSCloudStorage, file uploads function exactly as they would with AWS S3.

Server-Side Code

Uploading a file involves two main steps on the server:

  1. Creating an upload description: Grants the client temporary permission to upload directly to RustFS.
  2. Verifying the upload: Confirms the upload completed successfully.

Step 1: Get Upload Description

Future<String?> getUploadDescription(Session session, String path) async {
  return await session.storage.createDirectFileUploadDescription(
    storageId: 'public',
    path: path,
  );
}

Step 2: Verify Upload After the direct upload finishes, you must verify it. This is the only reliable way to know if the upload to third-party storage succeeded.

Future<bool> verifyUpload(Session session, String path) async {
  return await session.storage.verifyDirectFileUpload(
    storageId: 'public',
    path: path,
  );
}

Client-Side Code

On the client application, request the upload description, perform the upload (preferably using a Stream for large files to save memory), and then verify it with the server.

var uploadDescription = await client.myEndpoint.getUploadDescription('myfile');

if (uploadDescription != null) {
  var uploader = FileUploader(uploadDescription);
  // Assuming 'myStream' is a Stream of the file data
  await uploader.upload(myStream);
  var success = await client.myEndpoint.verifyUpload('myfile');
  
  if(success) {
      print('Upload successful!');
  }
}

5. File Path Best Practices

In production environments, file paths should typically be generated by the server, not the client, for security and consistency.

For compatibility with RustFS and other S3-style object storage systems, strictly adhere to these rules:

Valid Example:

'profile/$userId/images/avatar.png'

6. Accessing Stored Files

Serverpod provides convenient APIs for interacting with the stored files.

Check if a File Exists

var exists = await session.storage.fileExists(
  storageId: 'public',
  path: 'my/file/path',
);

Get a Public URL

If the file is in a public RustFS bucket and publicHost is configured, this will return a URL utilizing your custom domain.

var url = await session.storage.getPublicUrl(
  storageId: 'public',
  path: 'my/file/path',
);

Retrieve a File on the Server

You can also download the file’s binary data directly within your server-side Dart code:

var myByteData = await session.storage.retrieveFile(
  storageId: 'public',
  path: 'my/file/path',
);

Conclusion

By integrating RustFS, you gain the scalability of an S3-compatible object storage system while maintaining the developer-friendly workflow of Serverpod. Whether you are building a small app or a large-scale system, RustFS provides a solid and cost-effective foundation for your application’s file storage needs.