I recently stumbled upon minio when looking at the shrine doc while setting up tests related to file uploads.

One could say minio is like a self-hosted S3 object storage. It can be used on production systems as an amazon S3 (or other) alternative to store objects.

One other interesting aspect is to use it development and test environments when you already use a cloud provider for production. This allows you to test end-to-end file operations extensively without the need to mock some operations or network queries.

In my case, I use Scaleway object storage (S3 compatible), with the shrine gem, it looks like this :

s3_options = {
  bucket: ENV['S3_BUCKET'],
  access_key_id: ENV['S3_KEY_ID'],
  secret_access_key: ENV['S3_SECRET_KEY'],
  region: ENV['S3_REGION'],
  endpoint: ENV['S3_ENDPOINT'],
  force_path_style: true # This will be important for minio to work
}

Shrine.storages = {
  cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
  store: Shrine::Storage::S3.new(prefix: "invoices", **s3_options)
}

Installing Minio

I use docker-compose for my database and redis, adding minio was a breeze :

Adding a dedicated volume :

volumes:
  pg:
  redis:
  minio:

Then adding the service :

  minio:
    restart: always
    image: minio/minio:latest
    ports:
      - "9000:9000"
    volumes:
      - minio:/data
    entrypoint: minio server /data

If you’re not using docker, you can find instructions for installing minio locally in the docs.

There we go, we can browse to http://localhost:9000/ to make sure our minio instance is running, and create our bucket.

Integration in dev

In the development environment, all I had to do was to update my env variables for minio. If you keep the default credentials, it will look like this :

S3_KEY_ID=minioadmin
S3_SECRET_KEY=minioadmin
S3_BUCKET=bucket-name
S3_ENDPOINT=http://localhost:9000

If you use direct upload, you may need to tweak the javascript code that catches the path of the uploaded object, to make sure it works with both minio and whatever cloud provider you use, as the addresses may be formatted differently.

Integration in test

Same goes about the environment variables in test. You will also need to create a bucket for your test env.

This works but every time a new developer will need to set-up their system they will have to do this task, and if you use a CI it will be tedious. Moreover, each time you will perform an upload in your tests, your storage will grow.

To avoid any issues, we can programatically create and delete the storages before and after the test suite.

Here it is with shrine, but you can adapt this code to use your favorite adapter instead :

if ENV['S3_ENDPOINT'].include?('localhost')
  RSpec.configure do |config|
    config.before(:all) do
      Shrine.storages[:store].bucket.create unless Shrine.storages[:store].bucket.exists?
    end
    config.after(:all) do
      Shrine.storages[:cache].clear!
      Shrine.storages[:store].clear!
    end
  end
else
  puts("It doesn't seem like S3 is mocked in test, skipping auto clearing of bucket")
end

As you can see, I’m a bit paranoid of clearing the wrong bucket so I added a guard to make sure we are only cleaning something containing localhost.

Thanks for reading !