minio-storage-manager
AI-powered 3D asset generation platform for game development
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- GitHub stars: 2
- Updated: 2025-11-21
Download the latest version (v1.0.0) from this page.
SKILL.md
# MinIO Storage Manager
## Activation
When the user mentions any of these, activate this skill:
- "minio storage"
- "minio bucket"
- "s3 compatible storage"
- "object storage railway"
- "minio configuration"
- "minio client setup"
- "minio railway deployment"
- "file upload storage"
## Purpose
Manages MinIO object storage on Railway, including bucket creation, file operations, access control, and integration with applications. Provides S3-compatible storage for file uploads, media assets, backups, and data archiving.
## Context
MinIO is a high-performance, S3-compatible object storage system that can be deployed on Railway with one-click templates. It provides powerful object storage with familiar S3 APIs, making it easy to migrate from AWS S3 or implement object storage in new applications.
## Core Capabilities
### 1. MinIO Deployment on Railway
- Deploy MinIO using Railway templates
- Configure persistent storage volumes
- Set up access credentials
- Configure networking (public/private)
### 2. Bucket Management
- Create and delete buckets
- Configure bucket policies
- Set up bucket versioning
- Configure bucket lifecycle rules
### 3. File Operations
- Upload files and objects
- Download files
- Delete objects
- Generate presigned URLs for temporary access
- Multi-part uploads for large files
### 4. Access Control
- Configure access keys and secrets
- Set up bucket policies
- Configure IAM policies
- Public vs private bucket configuration
### 5. Client Integration
- MinIO JavaScript/TypeScript client
- AWS SDK S3 client (compatible)
- Python boto3 client
- MinIO CLI (mc) operations
## Implementation Patterns
### 1. Deploy MinIO on Railway
```bash
# Option 1: Deploy via Railway Dashboard
# Go to Railway Dashboard → New → Template → Search "MinIO"
# Click deploy and configure:
# - MINIO_ROOT_USER: admin username
# - MINIO_ROOT_PASSWORD: secure password (minimum 8 characters)
# Option 2: Deploy via CLI (using template)
railway up --service=minio
```
### 2. MinIO Configuration (Environment Variables)
```env
# MinIO Credentials
MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=your-secure-password-here
# MinIO Endpoint (Private Network)
MINIO_ENDPOINT=${{MinIO.RAILWAY_PRIVATE_DOMAIN}}
MINIO_PORT=${{MinIO.PORT}}
MINIO_USE_SSL=false # Use false for private network (http://)
# Public Endpoint (for external access)
MINIO_PUBLIC_ENDPOINT=https://${{MinIO.RAILWAY_STATIC_URL}}
# MinIO Server Configuration
MINIO_BROWSER=on # Enable MinIO Console
MINIO_REGION=us-east-1 # Default region
```
### 3. Application Integration
#### Node.js/TypeScript with MinIO Client
```typescript
import * as Minio from 'minio';
// Configure MinIO client
const minioClient = new Minio.Client({
endPoint: process.env.MINIO_ENDPOINT!,
port: parseInt(process.env.MINIO_PORT || '9000'),
useSSL: process.env.MINIO_USE_SSL === 'true',
accessKey: process.env.MINIO_ROOT_USER!,
secretKey: process.env.MINIO_ROOT_PASSWORD!,
});
// Create bucket
async function createBucket(bucketName: string): Promise<void> {
const exists = await minioClient.bucketExists(bucketName);
if (!exists) {
await minioClient.makeBucket(bucketName, 'us-east-1');
console.log(`Bucket ${bucketName} created successfully`);
}
}
// Upload file
async function uploadFile(
bucketName: string,
objectName: string,
filePath: string
): Promise<void> {
await minioClient.fPutObject(bucketName, objectName, filePath);
console.log(`File uploaded successfully: ${objectName}`);
}
// Upload from buffer
async function uploadBuffer(
bucketName: string,
objectName: string,
buffer: Buffer,
metadata?: Record<string, string>
): Promise<void> {
await minioClient.putObject(
bucketName,
objectName,
buffer,
buffer.length,
metadata
);
}
// Download file
async function downloadFile(
bucketName: string,
objectName: string,
filePath: string
): Promise<void> {
await minioClient.fGetObject(bucketName, objectName, filePath);
console.log(`File downloaded successfully: ${objectName}`);
}
// Get presigned URL (temporary public access)
async function getPresignedUrl(
bucketName: string,
objectName: string,
expirySeconds: number = 3600
): Promise<string> {
return await minioClient.presignedGetObject(
bucketName,
objectName,
expirySeconds
);
}
// List objects in bucket
async function listObjects(
bucketName: string,
prefix?: string
): Promise<string[]> {
const stream = minioClient.listObjectsV2(bucketName, prefix, true);
const objects: string[] = [];
return new Promise((resolve, reject) => {
stream.on('data', (obj) => objects.push(obj.name));
stream.on('end', () => resolve(objects));
stream.on('error', reject);
});
}
// Delete object
async function deleteObject(
bucketName: string,
objectName: string
): Promise<void> {
await minioClient.removeObject(bucketName, objectName);
console.log(`Object deleted: ${objectName}`);
}
// Set bucket policy (public read)
async function setPublicReadPolicy(bucketName: string): Promise<void> {
const policy = {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: { AWS: ['*'] },
Action: ['s3:GetObject'],
Resource: [`arn:aws:s3:::${bucketName}/*`],
},
],
};
await minioClient.setBucketPolicy(bucketName, JSON.stringify(policy));
}
// Initialize storage (create default buckets)
export async function initializeStorage(): Promise<void> {
const buckets = ['uploads', 'avatars', 'documents', 'backups'];
for (const bucket of buckets) {
await createBucket(bucket);
}
}
```
#### Using AWS SDK (S3-Compatible)
```typescript
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
// Configure S3 client for MinIO
const s3Client = new S3Client({
endpoint: `http://${process.env.MINIO_ENDPOINT}:${process.env.MINIO_PORT}`,
region: 'us-east-1',
credentials: {
accessKeyId: process.env.MINIO_ROOT_USER!,
secretAccessKey: process.env.MINIO_ROOT_PASSWORD!,
},
forcePathStyle: true, // Required for MinIO
});
// Upload file
async function uploadFileS3(
bucket: string,
key: string,
body: Buffer,
contentType?: string
): Promise<void> {
const command = new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: body,
ContentType: contentType,
});
await s3Client.send(command);
}
// Generate presigned URL
async function generatePresignedUrl(
bucket: string,
key: string,
expiresIn: number = 3600
): Promise<string> {
const command = new GetObjectCommand({
Bucket: bucket,
Key: key,
});
return await getSignedUrl(s3Client, command, { expiresIn });
}
```
#### Python Integration (boto3)
```python
import boto3
from botocore.client import Config
import os
# Configure MinIO client
s3_client = boto3.client(
's3',
endpoint_url=f"http://{os.getenv('MINIO_ENDPOINT')}:{os.getenv('MINIO_PORT')}",
aws_access_key_id=os.getenv('MINIO_ROOT_USER'),
aws_secret_access_key=os.getenv('MINIO_ROOT_PASSWORD'),
config=Config(signature_version='s3v4'),
region_name='us-east-1'
)
def create_bucket(bucket_name):
"""Create a bucket if it doesn't exist."""
try:
s3_client.head_bucket(Bucket=bucket_name)
except:
s3_client.create_bucket(Bucket=bucket_name)
print(f"Bucket {bucket_name} created")
def upload_file(bucket_name, file_path, object_name=None):
"""Upload a file to MinIO bucket."""
if object_name is None:
object_name = os.path.basename(file_path)
s3_client.upload_file(file_path, bucket_name, object_name)
print(f"File {object_name} uploaded successfully")
def download_file(bucket_name, object_name, file_path):
"""Download a file from MinIO bucket."""
s3_client.download_file(bucket_name, object_name, file_path)
print(f"File {object_name} downloaded successfully")
def generate_presigned_url(bucket_name, object_name, expiration=3600):
"""Generate a presigned URL for temporary access."""
url = s3_client.generate_presigned_url(
'get_object',
Params={'Bucket': bucket_name, 'Key': object_name},
ExpiresIn=expiration
)
return url
def list_objects(bucket_name, prefix=''):
"""List objects in a bucket."""
response = s3_client.list_objects_v2(Bucket=bucket_name, Prefix=prefix)
return [obj['Key'] for obj in response.get('Contents', [])]
```
### 4. MinIO CLI (mc) Commands
```bash
# Install MinIO client
# macOS
brew install minio/stable/mc
# Linux
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/
# Configure MinIO alias
mc alias set railway http://MINIO_ENDPOINT:PORT ACCESS_KEY SECRET_KEY
# Create bucket
mc mb railway/my-bucket
# Upload file
mc cp file.txt railway/my-bucket/
# Download file
mc cp railway/my-bucket/file.txt ./
# List buckets
mc ls railway
# List objects in bucket
mc ls railway/my-bucket
# Remove object
mc rm railway/my-bucket/file.txt
# Set public read policy
mc anonymous set download railway/my-bucket
# Mirror directory to bucket
mc mirror ./local-folder railway/my-bucket
# Set bucket versioning
mc version enable railway/my-bucket
```
### 5. File Upload API Example (Next.js)
```typescript
// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server';
import * as Minio from 'minio';
const minioClient = new Minio.Client({
endPoint: process.env.MINIO_ENDPOINT!,
port: parseInt(process.env.MINIO_PORT!),
useSSL: false,
accessKey: process.env.MINIO_ROOT_USER!,
secretKey: process.env.MINIO_ROOT_PASSWORD!,
});
export async function POST(request: NextRequest) {
try {
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json(
{ error: 'No file provided' },
{ status: 400 }
);
}
// Convert file to buffer
const buffer = Buffer.from(await file.arrayBuffer());
// Generate unique filename
const fileName = `${Date.now()}-${file.name}`;
// Upload to MinIO
await minioClient.putObject(
'uploads',
fileName,
buffer,
buffer.length,
{
'Content-Type': file.type,
}
);
// Generate URL (if bucket is public or use presigned URL)
const url = await minioClient.presignedGetObject(
'uploads',
fileName,
24 * 60 * 60 // 24 hours
);
return NextResponse.json({
success: true,
fileName,
url,
});
} catch (error) {
console.error('Upload error:', error);
return NextResponse.json(
{ error: 'Upload failed' },
{ status: 500 }
);
}
}
```
## Best Practices
### 1. Security
- **Use strong passwords** for MINIO_ROOT_USER and MINIO_ROOT_PASSWORD
- **Use private networking** for internal service communication
- **Rotate credentials regularly**
- **Use bucket policies** to restrict access
- **Never expose admin credentials** in client-side code
- **Use presigned URLs** for temporary public access instead of making buckets public
### 2. Bucket Organization
- **Create separate buckets** for different content types (uploads, avatars, documents)
- **Use consistent naming conventions** (lowercase, hyphens)
- **Use prefixes/folders** within buckets for organization
- **Example structure**:
- `uploads/users/{userId}/{fileName}`
- `avatars/{userId}.jpg`
- `documents/{companyId}/{documentId}.pdf`
### 3. Performance
- **Use private networking** between Railway services for faster transfers
- **Configure appropriate buffer sizes** for large files
- **Use multipart uploads** for files > 5MB
- **Implement caching** for frequently accessed objects
- **Use CDN** (Cloudflare, etc.) for public assets
### 4. Data Management
- **Enable versioning** for important buckets
- **Set up lifecycle rules** to archive or delete old objects
- **Implement backup strategy** for critical data
- **Monitor storage usage** to manage costs
### 5. Error Handling
- **Always validate file types** before upload
- **Implement retry logic** for failed uploads
- **Handle network errors gracefully**
- **Validate file sizes** before upload
- **Log errors** for debugging
### 6. Cost Optimization
- **Delete unused objects** regularly
- **Compress files** before upload when appropriate
- **Use lifecycle policies** to transition old data
- **Monitor storage usage** in Railway dashboard
## Common Workflows
### 1. Initial Setup
```typescript
// Initialize MinIO storage system
async function setupMinioStorage() {
// Create required buckets
const buckets = ['uploads', 'avatars', 'documents', 'backups'];
for (const bucket of buckets) {
const exists = await minioClient.bucketExists(bucket);
if (!exists) {
await minioClient.makeBucket(bucket, 'us-east-1');
console.log(`Created bucket: ${bucket}`);
}
}
// Set public read policy for avatars
const avatarPolicy = {
Version: '2012-10-17',
Statement: [{
Effect: 'Allow',
Principal: { AWS: ['*'] },
Action: ['s3:GetObject'],
Resource: ['arn:aws:s3:::avatars/*'],
}],
};
await minioClient.setBucketPolicy('avatars', JSON.stringify(avatarPolicy));
console.log('MinIO storage initialized successfully');
}
```
### 2. User Avatar Upload
```typescript
async function uploadUserAvatar(
userId: string,
avatarBuffer: Buffer,
contentType: string
): Promise<string> {
const objectName = `${userId}.jpg`;
await minioClient.putObject(
'avatars',
objectName,
avatarBuffer,
avatarBuffer.length,
{ 'Content-Type': contentType }
);
// Return public URL (if bucket is public)
return `${process.env.MINIO_PUBLIC_ENDPOINT}/avatars/${objectName}`;
}
```
### 3. Document Upload with Metadata
```typescript
async function uploadDocument(
documentId: string,
fileBuffer: Buffer,
metadata: {
userId: string;
fileName: string;
fileSize: number;
mimeType: string;
}
): Promise<void> {
const objectName = `${metadata.userId}/${documentId}`;
await minioClient.putObject(
'documents',
objectName,
fileBuffer,
metadata.fileSize,
{
'Content-Type': metadata.mimeType,
'X-Amz-Meta-User-Id': metadata.userId,
'X-Amz-Meta-Original-Name': metadata.fileName,
'X-Amz-Meta-Upload-Date': new Date().toISOString(),
}
);
}
```
### 4. Generate Temporary Access Link
```typescript
async function generateTemporaryLink(
bucketName: string,
objectName: string,
expiryHours: number = 24
): Promise<string> {
const expirySeconds = expiryHours * 60 * 60;
return await minioClient.presignedGetObject(
bucketName,
objectName,
expirySeconds
);
}
```
### 5. Backup System
```typescript
async function backupDatabase(
backupBuffer: Buffer,
timestamp: Date
): Promise<void> {
const fileName = `backup-${timestamp.toISOString()}.sql.gz`;
await minioClient.putObject(
'backups',
fileName,
backupBuffer,
backupBuffer.length,
{ 'Content-Type': 'application/gzip' }
);
// Set up lifecycle rule to delete backups older than 30 days
// (configure via MinIO Console or CLI)
}
```
## Troubleshooting
### Connection Issues
- Verify MINIO_ENDPOINT and MINIO_PORT are correct
- Check if using private network (use RAILWAY_PRIVATE_DOMAIN)
- Ensure MinIO service is running in Railway
- Verify credentials (MINIO_ROOT_USER, MINIO_ROOT_PASSWORD)
### Upload Failures
- Check file size limits
- Verify bucket exists
- Ensure sufficient storage quota
- Check network connectivity
- Validate credentials and permissions
### Access Denied Errors
- Verify bucket policies are correctly configured
- Check access key and secret key
- Ensure user has permission for the operation
- Verify bucket exists
### Performance Issues
- Use private networking for inter-service communication
- Implement multipart uploads for large files
- Consider implementing caching layer
- Check Railway resource limits
## Migration from AWS S3
MinIO is S3-compatible, making migration straightforward:
1. **Update endpoint**: Change from `s3.amazonaws.com` to MinIO endpoint
2. **Update credentials**: Use MinIO access keys
3. **Set forcePathStyle**: Add `forcePathStyle: true` for AWS SDK
4. **No code changes required**: S3 SDK commands work as-is
```typescript
// Before (AWS S3)
const s3Client = new S3Client({
region: 'us-east-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});
// After (MinIO)
const s3Client = new S3Client({
endpoint: `http://${process.env.MINIO_ENDPOINT}:${process.env.MINIO_PORT}`,
region: 'us-east-1',
credentials: {
accessKeyId: process.env.MINIO_ROOT_USER!,
secretAccessKey: process.env.MINIO_ROOT_PASSWORD!,
},
forcePathStyle: true, // Required for MinIO
});
```
## Resources
- MinIO Client SDK: https://www.npmjs.com/package/minio
- AWS SDK S3: https://www.npmjs.com/package/@aws-sdk/client-s3
- MinIO Documentation: https://min.io/docs/minio/linux/index.html
- Railway MinIO Template: https://railway.app/template/SMKOEA
- MinIO Console: Access via Railway public URL
## Notes
- MinIO requires persistent volumes - Railway handles this automatically
- Use HTTP (not HTTPS) for private network connections
- MinIO Console is accessible via public URL for admin operations
- Consider using CDN for public-facing assets
- Implement proper error handling and retry logic for production use