Automating MinIO File Cleanup

Apr 05, 2025 5 min read
KnowledgeRecommendedSecurityTools

Summary

I've been working on a fork of an end-to-end encrypted file transfer service called Sharrr lately. It's been a lot of fun and one of the better learning experiences I've had as of late.

One of the biggest challenges in getting it to work was to refactor the app to work with more than one S3 provider while still maintaining the same level of security...oh, and also not breaking it in the process. After getting this to work with Backblaze, Storj, and MinIO, I decided that the best one for my needs was MinIO since I could run it locally.

However, another challenge was how do I keep my server from filling up with old files? But first, a bit of background on how I got here.

Background

This section could certainly be its own blog post, so I'm going to keep it as brief as I can without getting too far away from the scope of this post.

One of the biggest challenges I encountered while digging into this project was that no matter which S3-compatible service I used, file uploads were consistently failing due to CORS (Cross-Origin Resource Sharing) errors. This didn't make much sense because I had CORS set on the S3 bucket to be as permissive as possible.

What was happening was that the app was attempting to make direct PUT requests from the browser, but the preflight checks were failing. I didn't want to spend too much time troubleshooting, but it seemed that there were some compatibility limitations with presigned POST operations. I decided to create an upload proxy to completely bypass CORS and upload the chunks directly to S3.

At the time, I was using Backblaze B2, and after implementing this change, the uploads started working seamlessly. I also use Storj for my NAS backups so I tried it there as well, which worked like a charm. However, what I didn't want was to incur costs from all of this. Since I already have a server with ample storage space, I decided to setup MinIO.

The Challenge

I recently noticed that my MinIO storage was growing rapidly with temporary files that were no longer needed. The project is configured with a cleanup script that triggers GitHub Actions to cleanup the S3 bucket. However, as I have my instance protected with Cloudflare Access, it was causing a redirect issue and sending the request to the authentication page. While this likely could've been solved with Cloudflare Access Service Tokens, I decided to take the GitHub Actions workflow entirely out of the equation and instead use a local cleanup script that leverages the MinIO Client (mc).

Setting up the MinIO Client

First, I needed to install the MinIO Client on my Ubuntu server:

1wget https://dl.min.io/client/mc/release/linux-amd64/mc 2chmod +x mc 3sudo mv mc /usr/local/bin/

After installation, I verified it was working with mc --version.

Next, I configured an alias to connect to my MinIO server:

1mc alias set myminio http://localhost:9000 MY_ACCESS_KEY MY_SECRET_KEY

A quick test confirmed the connection was working:

1mc ls myminio

Cleanup Script

I needed to create a bash script to handle the cleanup process using mc. The following script ended up working just fine:

1#!/bin/bash 2 3# Set variables 4MINIO_ENDPOINT="http://localhost:9000" 5BUCKET_NAME="my-bucket" 6RETENTION_DAYS=7 7ACCESS_KEY="my-access-key" 8SECRET_KEY="my-secret-key" 9 10# Configure mc client 11mc alias remove myminio 2>/dev/null || true 12mc alias set myminio $MINIO_ENDPOINT $ACCESS_KEY $SECRET_KEY 13 14# Verify connection and bucket existence 15echo "Verifying connection to MinIO server..." 16if ! mc ls myminio/$BUCKET_NAME > /dev/null 2>&1; then 17 echo "Error: Cannot access bucket. Please check your credentials and bucket name." 18 exit 1 19fi 20 21# Log start time 22echo "Starting MinIO cleanup at $(date)" 23 24# Find and delete files older than retention period 25echo "Finding files older than $RETENTION_DAYS days in bucket $BUCKET_NAME..." 26 27# Use mc find to locate and delete old files 28mc find myminio/$BUCKET_NAME --older-than "${RETENTION_DAYS}d" --exec "mc rm --force {}" 29 30echo "Cleanup completed at $(date)"

I saved it in my $HOME directory and made it executable:

1chmod +x /home/<user>/scripts/minio-cleanup.sh

I set the retention period to 7 days because the app is configured by default to retain files for 7 days. This giving recipients ample time to download their files while ensuring my storage doesn't become cluttered with abandoned transfers.

Permissions Issue

My first attempt at running the script failed with an "Access Denied" error.

access-denied

I logged into MinIO and checked the access key. After inspecting the policy, I realized I was missing the s3:DeleteObject permission. I added it in and ensured the following policy was attached:

1{ 2 "Version": "2012-10-17", 3 "Statement": [ 4 { 5 "Effect": "Allow", 6 "Action": [ 7 "s3:GetObject", 8 "s3:ListBucket", 9 "s3:PutObject", 10 "s3:DeleteObject" 11 ], 12 "Resource": [ 13 "arn:aws:s3:::my-bucket", 14 "arn:aws:s3:::my-bucket/*" 15 ] 16 } 17 ] 18}

I re-ran the script and this time, after removing ~200 files, it completed successfully.

deletion-success

Taking it a Step Further

The only thing I felt was missing at this point was a way for me to find out if the script failed, other than finding out the hard way that I've run out of space. Since I already use healthchecks.io for a lot of my monitoring, it made sense to include this as well.

I created a new check and added it to my crontab:

10 1 * * * /home/<user>/scripts/minio-cleanup.sh && curl -fsS -m 10 --retry 5 -o /dev/null https://hc-ping.com/my-unique-uuid

I ran the script for good measure with the curl call to healthchecks.io and it completed successfully as expected.

cronjob

Conclusion

Automated file cleanup may seem like a small detail nowadays considering how inexpensive storage is, but it's one of those things that's easy to grow out of control. This is a small quality of life addition that allows me to focus on other things while keeping my MinIO storage in check with zero ongoing effort.