MinIO is the leading open-source S3-compatible object storage server — deploy it on your Hong Kong VPS and every application that supports Amazon S3 (using the AWS SDK, boto3, or any S3 client) works with your self-hosted storage with no code changes except the endpoint URL.
For Asia-Pacific applications, self-hosted MinIO on Hong Kong VPS eliminates AWS S3 data transfer costs, keeps data in Hong Kong jurisdiction, and provides S3-compatible storage accessible from mainland China at CN2 GIA latency — versus AWS ap-east-1 S3 at significantly higher cost with metered egress pricing.
Step 1: Deploy MinIO with Docker
mkdir -p /home/deploy/minio /mnt/minio-data
cd /home/deploy/minio
nano docker-compose.ymlversion: '3.8'
services:
minio:
image: quay.io/minio/minio:latest
container_name: minio
restart: unless-stopped
ports:
- "127.0.0.1:9000:9000" # S3 API endpoint
- "127.0.0.1:9001:9001" # Web console
volumes:
- /mnt/minio-data:/data
environment:
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
- MINIO_SITE_NAME=hk-storage
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 5s
retries: 5nano .envMINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=strong_minio_password_min_8_charschmod 600 .env
docker compose up -dStep 2: Configure Nginx for MinIO API and Console
nano /etc/nginx/sites-available/minioserver {
listen 443 ssl http2;
server_name storage.yourdomain.com; # S3 API endpoint
ssl_certificate /etc/letsencrypt/live/storage.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/storage.yourdomain.com/privkey.pem;
# MinIO requires large uploads
client_max_body_size 0;
proxy_request_buffering off;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_buffering off;
}
}
server {
listen 443 ssl http2;
server_name storage-console.yourdomain.com; # MinIO console
ssl_certificate /etc/letsencrypt/live/storage-console.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/storage-console.yourdomain.com/privkey.pem;
# Restrict console to admin IPs only
allow YOUR_HOME_IP;
deny all;
location / {
proxy_pass http://127.0.0.1:9001;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}certbot --nginx -d storage.yourdomain.com -d storage-console.yourdomain.com \
--email your@email.com --agree-tos --no-eff-email
nginx -t && systemctl reload nginxStep 3: Create Buckets and Users
# Install MinIO client (mc)
curl https://dl.min.io/client/mc/release/linux-amd64/mc -o /usr/local/bin/mc
chmod +x /usr/local/bin/mc
# Configure mc to connect to your MinIO instance
mc alias set hk-storage https://storage.yourdomain.com admin strong_minio_password
# Create buckets
mc mb hk-storage/uploads # User upload storage
mc mb hk-storage/media # Media files (images, videos)
mc mb hk-storage/backups # Automated backups
mc mb hk-storage/public # Publicly readable files
# Set public bucket policy (allow anonymous GET)
mc anonymous set public hk-storage/public
# Create application-specific user with limited permissions
mc admin user add hk-storage myapp_user strong_app_password
# Create policy for myapp_user (read/write to specific buckets only)
cat > /tmp/myapp-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": ["arn:aws:s3:::uploads/*", "arn:aws:s3:::media/*"]
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::uploads", "arn:aws:s3:::media"]
}
]
}
EOF
mc admin policy create hk-storage myapp-policy /tmp/myapp-policy.json
mc admin policy attach hk-storage myapp-policy --user myapp_userStep 4: Integrate with Applications
Node.js (AWS SDK v3)
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presignerconst { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
const s3 = new S3Client({
endpoint: 'https://storage.yourdomain.com',
region: 'us-east-1', // Required but ignored by MinIO
credentials: {
accessKeyId: 'myapp_user',
secretAccessKey: 'strong_app_password'
},
forcePathStyle: true // Required for MinIO (not AWS S3)
});
// Upload file
async function uploadFile(key, body, contentType) {
await s3.send(new PutObjectCommand({
Bucket: 'uploads',
Key: key,
Body: body,
ContentType: contentType
}));
return `https://storage.yourdomain.com/uploads/${key}`;
}
// Generate pre-signed upload URL (browser direct upload)
async function getUploadUrl(key, contentType) {
return getSignedUrl(s3, new PutObjectCommand({
Bucket: 'uploads',
Key: key,
ContentType: contentType
}), { expiresIn: 3600 });
}
// Generate pre-signed download URL
async function getDownloadUrl(key) {
return getSignedUrl(s3, new GetObjectCommand({
Bucket: 'uploads',
Key: key
}), { expiresIn: 86400 });
}Python (boto3)
pip install boto3import boto3
from botocore.client import Config
s3 = boto3.client(
's3',
endpoint_url='https://storage.yourdomain.com',
aws_access_key_id='myapp_user',
aws_secret_access_key='strong_app_password',
config=Config(signature_version='s3v4')
)
# Upload
s3.upload_file('local_file.jpg', 'uploads', 'remote/path/file.jpg')
# Download
s3.download_file('uploads', 'remote/path/file.jpg', 'local_download.jpg')
# List objects
response = s3.list_objects_v2(Bucket='uploads', Prefix='user-123/')
for obj in response.get('Contents', []):
print(obj['Key'], obj['Size'])Step 5: Configure Lifecycle Rules for Automatic Cleanup
# Automatically delete temp uploads after 24 hours
mc ilm rule add --expiry-days 1 hk-storage/uploads --prefix "temp/"
# Move objects to cheaper storage class after 30 days
# (MinIO supports STANDARD and REDUCED_REDUNDANCY tiers)
mc ilm rule add --transition-days 30 --storage-class REDUCED_REDUNDANCY hk-storage/media
# View lifecycle rules
mc ilm rule ls hk-storage/uploadsConclusion
Self-hosted MinIO on a Hong Kong VPS provides S3-compatible object storage with zero egress fees, Hong Kong data residency, and CN2 GIA latency for Chinese application access. Any application built for AWS S3 works with MinIO with only the endpoint URL changed — making migration straightforward and cost savings immediate.
Deploy MinIO on Server.HK’s NVMe SSD Hong Kong VPS plans — fast local storage for object reads and writes, with room to expand via additional volumes as your storage needs grow.
Frequently Asked Questions
How does MinIO pricing compare to AWS S3 for Hong Kong VPS workloads?
AWS S3 in ap-east-1 charges $0.025/GB/month for storage plus $0.12/GB outbound transfer. MinIO self-hosted costs only the VPS storage (included in your VPS plan) and VPS bandwidth (unmetered on Server.HK). For 100 GB storage with 500 GB monthly transfers, AWS costs ~$62.50/month in data charges alone vs effectively $0 additional cost with self-hosted MinIO.
Is MinIO production-ready for enterprise workloads?
Yes. MinIO is used in production by major enterprises and is the object storage layer for many Kubernetes-based data platforms. Single-node MinIO (as deployed in this guide) has a single point of failure — for high-availability object storage, deploy MinIO in distributed mode across multiple nodes or VPS instances, where data is erasure-coded across nodes for fault tolerance.
Can I use MinIO as a backend for Nextcloud?
Yes. Nextcloud’s external storage feature supports S3-compatible storage — configure it with your MinIO endpoint, access key, and secret key to use MinIO buckets as Nextcloud user storage. This separates file storage (MinIO, scalable) from application logic (Nextcloud on VPS), allowing each to scale independently.