Improving local file storage in serverpod

The Problem with Database File Storage in Local Dev

If you’ve been building with Serverpod for any length of time, you’ve probably run into the same friction we did. You upload a file in local development, something breaks — the request is too large, the generated URL doesn’t work the way you expect, or you need to debug what’s actually stored and find yourself squinting at binary blobs in a Postgres row. It works, but it doesn’t feel right.

We’ve been hitting these walls repeatedly, so we built a fix — and we think it should become the default for every new Serverpod project.


Serverpod ships with a database-backed file storage adapter out of the box. For getting started quickly, it’s convenient — there’s nothing extra to spin up. But the convenience comes with real trade-offs:

Maximum request size limits. PostgreSQL isn’t designed for large binary payloads. In local development you’ll regularly bump into upload size caps that don’t reflect what your production S3 bucket will accept. You end up tuning limits just for local, and the gap between environments grows.

URL handling inconsistencies. The URLs generated by database storage behave differently from S3-style public URLs. Code that constructs or consumes file URLs locally may silently behave differently in production.

No visibility. To see what files are stored locally, you have to connect to the database and inspect blobs. There’s no way to browse your uploads, verify file structure, or quickly delete a test file without writing SQL.

It’s not what production looks like. Your staging and production environments almost certainly use an S3-compatible service. Local development should too.


The Solution: RustFS in Docker

RustFS is an open-source, S3-compatible object storage server. It’s lightweight, runs in Docker, and — critically — speaks the same API as AWS S3, Google Cloud Storage, or any other S3-compatible service you might use in production.

The idea is simple: instead of using database storage locally, add RustFS to your docker-compose.yaml. From Serverpod’s perspective, it’s just another S3 endpoint. Your file handling code doesn’t need to change between environments — only the configuration does.

We’ve been using this setup locally at our company and it’s eliminated an entire category of „worked on my machine” surprises during development.


What You Get

A visual storage UI. RustFS ships with a web console at http://localhost:9001. You can browse buckets, upload test files, download them, and delete them — all without touching the database. This alone is worth the switch for debugging.

True production parity. Because RustFS speaks the S3 API, your local file handling code behaves identically to how it will against any S3-compatible cloud provider in production. URL formats, access patterns, public/private bucket behaviour — all consistent. No more „it worked locally” surprises.

Better performance. Dedicated object storage is meaningfully faster than reading and writing binary data through Postgres, especially for larger files.


Tutorial: Setting It Up in Your Serverpod Project

This setup has three parts: adding RustFS to Docker Compose, adding a named volume, and wiring up the Dart package on the server side.

Prerequisites

  • A Serverpod project with docker-compose.yaml (generated by serverpod create)
  • Docker and Docker Compose installed

Step 1: Add RustFS to docker-compose.yaml

Open your project’s docker-compose.yaml and add two new services — rustfs and create_buckets — alongside your existing postgres and serverpod services:

  rustfs:
    image: rustfs/rustfs:latest
    container_name: rustfs_server
    ports: ["9001:9001"]
    environment:
      - RUSTFS_ACCESS_KEY=rustfsadmin
      - RUSTFS_SECRET_KEY=rustfsadmin_secret
    volumes: ["myapp_files:/data"]
    command: /data

  create_buckets:
    image: minio/mc
    depends_on: [rustfs]
    entrypoint: >
      /bin/sh -c "
      until /usr/bin/mc alias set myrustfs http://rustfs_server:9000 rustfsadmin rustfsadmin_secret; do sleep 2; done;
      /usr/bin/mc mb myrustfs/serverpod --ignore-existing;
      /usr/bin/mc anonymous set public myrustfs/serverpod;
      exit 0;
      "

Replace myapp with your actual project name (the same prefix used for myapp_data and myapp_test_data).

The create_buckets service runs once on startup using the MinIO client (mc) to create a default serverpod bucket and set it to public — matching the behaviour Serverpod expects.

Step 2: Add the Named Volume

At the bottom of docker-compose.yaml, in the volumes: section, add a new entry for file storage:

volumes:
  myapp_data:
  myapp_files:        # <-- add this
  myapp_test_data:

This gives files a dedicated persistent volume separate from your database data.

Step 3: Add the Dart Package

In your server package’s pubspec.yaml, add the dependency:

dependencies:
  serverpod_cloud_storage_rustfs: ^1.0.0

Run:

dart pub get

Step 4: Register the Storage Adapter

In your server’s server.dart (or wherever you initialise your Serverpod pod), register the RustFS cloud storage before calling pod.start():

import 'package:serverpod_cloud_storage_rustfs/serverpod_cloud_storage_rustfs.dart';

// Inside your server setup:
pod.addCloudStorage(RustFsCloudStorage(
  serverpod: pod,
  storageId: 'public',
  public: true,
  bucket: 'serverpod',
));

No credentials are needed for local development — the package defaults to rustfsadmin / rustfsadmin_secret, which matches what you set in docker-compose.yaml.

⚠️ Security warning: The default credentials are intended for local development only. Never use them outside your local machine. If you expose the RustFS container beyond localhost for any reason, change RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY in docker-compose.yaml before starting the stack.

Step 5: Start the Stack

docker compose up -d

Docker will pull the RustFS image, start the server, and the create_buckets service will automatically provision the default bucket. Once everything is running, open your browser to:

http://localhost:9001

Log in with rustfsadmin / rustfsadmin_secret. You’ll see the storage console where you can browse uploaded files as you develop.

Step 6: Enable CORS (Flutter Web only)

If your app targets Flutter Web, you must enable CORS for the serverpod bucket, otherwise file requests from the browser will be blocked and storage will not work.

Open the RustFS console at http://localhost:9001, navigate to the serverpod bucket settings, and enable the CORS configuration for that bucket. No code changes are required — this is a one-time step done entirely through the UI after the stack is running.


What About Production?

This setup is intentionally scoped to local development. In production you’d swap the RustFS adapter for whichever S3-compatible service your project uses — AWS S3, Cloudflare R2, Backblaze B2, etc. — using Serverpod’s existing serverpod_cloud_storage_s3_compat package.

That said, RustFS itself is production-ready. If you prefer to stay in control of your storage infrastructure, you can self-host RustFS on your own VPS or cloud instance and point the baseUri parameter at it:

pod.addCloudStorage(RustFsCloudStorage(
  serverpod: pod,
  storageId: 'public',
  public: true,
  bucket: 'serverpod',
  baseUri: Uri(scheme: 'https', host: 'rustfs.example.com'),
));

Either way, because both sides speak S3, the switch from local to production is purely a config change. No code changes, no surprises.


What We’re Proposing to the Serverpod Team

We’ve opened a discussion in the Serverpod repository to propose that this approach — or something equivalent — becomes the default when a new Serverpod project is created.

Concretely, that would mean:

  • The docker-compose.yaml generated by serverpod create ships with RustFS pre-configured
  • A named volume for file storage is included by default
  • Database-backed file storage is deprecated in favour of the S3 adapter

If you find this useful, head over to the discussion and let the Serverpod team know. The more real-world signal they get, the stronger the case for making a proper local file storage experience the default for everyone.


The Package

The Dart package is published on pub.dev:

serverpod_cloud_storage_rustfs

Source code and issue tracker: github.com/thecodebrothers/serverpod_cloud_storage_rustfs

Note: This is a community package, not an official Serverpod library — but it builds on serverpod_cloud_storage_s3_compat, which is part of the official ecosystem.


Published by thecodebrothers.pl

Przegląd prywatności

Ta strona korzysta z ciasteczek, aby zapewnić Ci najlepszą możliwą obsługę. Informacje o ciasteczkach są przechowywane w przeglądarce i wykonują funkcje takie jak rozpoznawanie Cię po powrocie na naszą stronę internetową i pomaganie naszemu zespołowi w zrozumieniu, które sekcje witryny są dla Ciebie najbardziej interesujące i przydatne. Zapoznaj się z naszą polityką prywatności.