Self-Hosted Object-Storage: S3 With MinIO

Host your own AWS S3 locally for free!

Self-Hosted Object-Storage: S3 With MinIO

Simple Storage Service or better known as S3 is an object-based storage service that is very popular for it's simplicity, go figure. Unlike a file-system there is no hierarchy of directories and different file types so it's incredible scalable and doesn't require a protocol layer like NFS or SMB/Samba. If you have delved into self-hosting larger projects, you will probably run into the ability to use S3 as a file-backend for services like GitLab or Sentry. It is also very popular for backups due to native object-versioning and simple API allowing you to connect to make hosted-services like AWS S3, Google Cloud Storage, or Huawei OBS.

Usually you would need to pay one of these cloud providers for usage and depending on the storage class it might be quite expensive for you. Instead, you can run your own block-storage server using MinIO. A free* fully-featured S3 hosting solution.

Recently MinIO has decided to stop releasing free docker images for their software. You will not get security updates should you choose to use the last available free version

All you need is docker and ideally a reverse-proxy setup for easy access.

Installation

docker run -d \
--name MinIO \
-p 9001:9001 \
-p 9000:9000 \
-v {STORAGE_PATH}:/config \
--restart unless-stopped \
-e MINIO_ROOT_USER={USERNAME} \
-e MINIO_ROOT_PASSWORD={PASSWORD} \
ghcr.io/imagegenius/minio:RELEASE.2025-04-22T22-12-26Z-ig64

## One-Liner ##
docker run -d --name MinIO -p 9001:9001 -p 9000:9000 -v {STORAGE_PATH}:/config --restart unless-stopped -e MINIO_ROOT_USER={USERNAME} -e MINIO_ROOT_PASSWORD={PASSWORD} ghcr.io/imagegenius/minio:RELEASE.2025-04-22T22-12-26Z-ig64

Port 9001 is for the WebUI and port 9000 is the API port that you will use whenever you want to use the S3 API. The storage path is where the config and the object files will be stored, I recommend using a bind mount instead of a Docker volume.

Once it's setup you can browse to the WebUI by going to http://{SERVER_IP}:9001 or setting up your reverse proxy. Go ahead and create a new bucket. MinIO gives you a nice explanation of each setting on the right. You can setup the bucket to have public access, where anyone with the link to the objects can download, update, and delete objects in the bucket. Or you can setup private access and use access keys and policies to limit access to the bucket and it's contents.

Creating Restricted Access Keys

Let's start with setting up access keys that give full access to just that bucket. For this example the bucket will be called my-minio-bucket.

Navigate to "Access Keys" on the left and click "Create access key" on the right. The Access Key and Secret Key will be prefilled. You can save them now if you'd like. I recommend saving them as a note in your password manager as the Secret Key will not be shown again. You can set a date and time that the keys expire if you don't want them to be perpetually available. Fill in a name and simple description so you know how this key will be used and by what.

Now let's setup permissions, it might seem daunting but most of the time you only want to restrict to read-only or read-write for a bucket or set of buckets and that is simple enough. Enable "Restrict beyond user policy" and fill in one of the following:

# For Full Access To Your Bucket
{
 "Version": "2012-10-17",
 "Statement": [
  {
   "Effect": "Allow",
   "Action": [
    "s3:*"
   ],
   "Resource": [
    "arn:aws:s3:::my-minio-bucket/*"
   ]
}]}
# For Read Only Access To Your Bucket
{
 "Version": "2012-10-17",
 "Statement": [
  {
   "Effect": "Allow",
   "Action": [
    "s3:Get*",
    "s3:List*"
   ],
   "Resource": [
    "arn:aws:s3:::my-minio-bucket/*"
   ]
}]}
💡
MinIO uses the same permissions system as AWS, here is a nice reference resource that lists them all and what they do: https://aws.permissions.cloud/iam/s3

You can now use your preferred S3 CLI, I'll use AWS S3

$ aws configure
# Enter the Access Key and Secret Key provided
# Use 'minio' as the region
# Use json as the default output

$ aws s3 ls --endpoint-url http://{MINIO_IP}:9000/
2025-10-30 14:13:05 my-minio-bucket

# You should see something like this ^^

Voila! You're all setup with you own local S3 service for any of your other services to use!