Deploy MinIO on Kubernetes using the operator for scalable object storage. Configure persistent volumes, high availability tenants, and secure ingress with SSL certificates.
Prerequisites
- Kubernetes cluster with admin access
- kubectl configured
- Ingress controller installed
- Storage class configured
- cert-manager for SSL certificates
What this solves
MinIO provides S3-compatible object storage that integrates seamlessly with Kubernetes workloads. This tutorial shows you how to deploy MinIO using the Kubernetes operator for production-grade object storage with persistent volumes, high availability configuration, and secure access through ingress controllers.
Prerequisites
- Kubernetes cluster with kubectl configured
- Cluster admin permissions
- At least 3 worker nodes for high availability
- Storage class configured (local-path, nfs, or cloud provider)
- Ingress controller installed (nginx-ingress recommended)
Step-by-step installation
Install MinIO Kubernetes operator
Download and apply the MinIO operator which manages MinIO tenants and handles updates, scaling, and configuration management.
kubectl apply -k "github.com/minio/operator?ref=v6.0.4"
Verify operator installation
Check that the MinIO operator pods are running in the minio-operator namespace.
kubectl get pods -n minio-operator
kubectl get crd | grep minio
Create storage class for MinIO
Define a storage class with appropriate performance characteristics for MinIO workloads. This example uses local storage but adjust for your environment.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: minio-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
type: local
kubectl apply -f minio-storage-class.yaml
Create persistent volumes
Create persistent volumes for MinIO data storage. In production, use dedicated disks or network storage for better performance and reliability.
apiVersion: v1
kind: PersistentVolume
metadata:
name: minio-pv-1
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: minio-storage
hostPath:
path: /mnt/minio-storage-1
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: minio-pv-2
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: minio-storage
hostPath:
path: /mnt/minio-storage-2
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: minio-pv-3
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: minio-storage
hostPath:
path: /mnt/minio-storage-3
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: minio-pv-4
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: minio-storage
hostPath:
path: /mnt/minio-storage-4
kubectl apply -f minio-pv.yaml
Create MinIO tenant namespace
Create a dedicated namespace for the MinIO tenant to isolate resources and apply specific policies.
kubectl create namespace minio-tenant
Deploy MinIO tenant with high availability
Create a MinIO tenant configuration with multiple servers for high availability and data redundancy.
apiVersion: minio.min.io/v2
kind: Tenant
metadata:
name: minio-cluster
namespace: minio-tenant
spec:
image: minio/minio:RELEASE.2024-01-16T16-07-38Z
credsSecret:
name: minio-creds-secret
pools:
- servers: 4
name: pool-0
volumesPerServer: 1
volumeClaimTemplate:
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: minio-storage
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 500m
memory: 1Gi
mountPath: /export
subPath: /data
requestAutoCert: false
certConfig:
commonName: ""
organizationName: []
dnsNames: []
externalCaCertSecret: []
externalCertSecret: []
externalClientCertSecrets: []
configuration:
name: minio-config-secret
Create MinIO credentials secret
Generate secure credentials for MinIO admin access. Use strong passwords in production environments.
kubectl create secret generic minio-creds-secret \
--from-literal=accesskey="minio-admin" \
--from-literal=secretkey="minio-secret-key-change-me" \
-n minio-tenant
Create MinIO configuration secret
Configure MinIO server settings including region, browser access, and performance tuning options.
kubectl create secret generic minio-config-secret \
--from-literal=config.env="export MINIO_REGION_NAME=us-east-1
export MINIO_BROWSER=on
export MINIO_SERVER_URL=https://minio.example.com
export MINIO_BROWSER_REDIRECT_URL=https://console.example.com" \
-n minio-tenant
Deploy the MinIO tenant
Apply the tenant configuration to create the MinIO cluster with high availability setup.
kubectl apply -f minio-tenant.yaml
Wait for tenant deployment
Monitor the deployment progress and ensure all pods reach running state before proceeding.
kubectl get pods -n minio-tenant -w
Create TLS certificates for secure access
Generate TLS certificates for MinIO API and console access. This example uses cert-manager for automatic certificate management.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: minio-api-tls
namespace: minio-tenant
spec:
secretName: minio-api-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
commonName: minio.example.com
dnsNames:
- minio.example.com
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: minio-console-tls
namespace: minio-tenant
spec:
secretName: minio-console-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
commonName: console.example.com
dnsNames:
- console.example.com
kubectl apply -f minio-certificates.yaml
Configure ingress for MinIO API
Create ingress resource to expose MinIO API endpoint with SSL termination and proper headers for S3 compatibility.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minio-api-ingress
namespace: minio-tenant
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "1024m"
nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- minio.example.com
secretName: minio-api-tls
rules:
- host: minio.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: minio-cluster-hl
port:
number: 9000
kubectl apply -f minio-api-ingress.yaml
Configure ingress for MinIO console
Create ingress for the MinIO web console with proper WebSocket support for real-time monitoring.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minio-console-ingress
namespace: minio-tenant
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "1024m"
nginx.ingress.kubernetes.io/websocket-services: minio-cluster-console
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- console.example.com
secretName: minio-console-tls
rules:
- host: console.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: minio-cluster-console
port:
number: 9090
kubectl apply -f minio-console-ingress.yaml
Configure network policies for security
Implement network policies to restrict traffic to MinIO services and enhance security posture.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: minio-network-policy
namespace: minio-tenant
spec:
podSelector:
matchLabels:
app: minio
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 9000
- protocol: TCP
port: 9090
- from:
- podSelector:
matchLabels:
app: minio
ports:
- protocol: TCP
port: 9000
egress:
- to: []
ports:
- protocol: TCP
port: 53
- protocol: UDP
port: 53
- to:
- podSelector:
matchLabels:
app: minio
ports:
- protocol: TCP
port: 9000
kubectl apply -f minio-network-policy.yaml
Set up monitoring with Prometheus integration
Configure MinIO to expose metrics for Prometheus monitoring and create service monitor for automatic discovery.
apiVersion: v1
kind: Service
metadata:
name: minio-cluster-metrics
namespace: minio-tenant
labels:
app: minio
monitoring: prometheus
spec:
ports:
- name: http-metrics
port: 9000
protocol: TCP
targetPort: 9000
selector:
app: minio
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: minio-cluster-monitor
namespace: minio-tenant
labels:
app: minio
spec:
selector:
matchLabels:
app: minio
monitoring: prometheus
endpoints:
- port: http-metrics
interval: 30s
path: /minio/v2/metrics/cluster
scheme: http
kubectl apply -f minio-monitoring.yaml
Configure backup automation
Set up automated backups using MinIO mc client in a CronJob for data protection and disaster recovery.
apiVersion: batch/v1
kind: CronJob
metadata:
name: minio-backup
namespace: minio-tenant
spec:
schedule: "0 2 *" # Daily at 2 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: minio-backup
image: minio/mc:latest
command:
- /bin/sh
- -c
- |
mc alias set source https://minio.example.com $MINIO_ACCESS_KEY $MINIO_SECRET_KEY
mc alias set backup s3://backup-bucket $BACKUP_ACCESS_KEY $BACKUP_SECRET_KEY
mc mirror source/ backup/$(date +%Y-%m-%d)/
env:
- name: MINIO_ACCESS_KEY
valueFrom:
secretKeyRef:
name: minio-creds-secret
key: accesskey
- name: MINIO_SECRET_KEY
valueFrom:
secretKeyRef:
name: minio-creds-secret
key: secretkey
- name: BACKUP_ACCESS_KEY
valueFrom:
secretKeyRef:
name: backup-credentials
key: access-key
- name: BACKUP_SECRET_KEY
valueFrom:
secretKeyRef:
name: backup-credentials
key: secret-key
restartPolicy: OnFailure
kubectl apply -f minio-backup-cronjob.yaml
Verify your setup
Check that all components are running and accessible:
# Check MinIO tenant status
kubectl get tenant -n minio-tenant
Verify all pods are running
kubectl get pods -n minio-tenant
Check persistent volume claims
kubectl get pvc -n minio-tenant
Test ingress connectivity
curl -I https://minio.example.com/minio/health/live
Check MinIO cluster status via mc client
kubectl run -it --rm minio-client --image=minio/mc:latest --restart=Never -- \
mc alias set k8s-minio https://minio.example.com minio-admin minio-secret-key-change-me
kubectl run -it --rm minio-client --image=minio/mc:latest --restart=Never -- \
mc admin info k8s-minio
Access the MinIO console at https://console.example.com using the credentials from your secret.
Configure client applications
Applications can access MinIO using standard S3 SDKs. Here's a configuration example for connecting applications:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-minio-config
data:
endpoint: "https://minio.example.com"
region: "us-east-1"
bucket: "app-data"
---
apiVersion: v1
kind: Secret
metadata:
name: app-minio-credentials
type: Opaque
data:
access-key: bWluaW8tYWRtaW4= # base64 encoded
secret-key: bWluaW8tc2VjcmV0LWtleS1jaGFuZ2UtbWU= # base64 encoded
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Pods stuck in Pending state | No available persistent volumes | Create more PVs or check storage class configuration |
| MinIO API returns 503 errors | Not enough healthy servers for quorum | Ensure at least N/2+1 servers are running where N is total servers |
| Console shows "Invalid credentials" | Wrong secret values or encoding | Verify secret contains correct base64 encoded values |
| Ingress returns 502 Bad Gateway | Service name mismatch or wrong ports | Check service names match ingress backend configuration |
| TLS certificate not working | cert-manager not properly configured | Verify ClusterIssuer exists and check certificate status |
| Poor performance during I/O operations | Storage not optimized for MinIO | Use dedicated SSDs and adjust storage class parameters |
| Backup job failing | Network policies blocking external access | Update network policies to allow egress for backup destinations |
Production optimization
For production deployments, consider these additional configurations:
Resource quotas and limits
Set appropriate resource quotas to prevent resource exhaustion and ensure predictable performance.
apiVersion: v1
kind: ResourceQuota
metadata:
name: minio-quota
namespace: minio-tenant
spec:
hard:
requests.cpu: "2"
requests.memory: 4Gi
limits.cpu: "4"
limits.memory: 8Gi
persistentvolumeclaims: "8"
Pod disruption budgets
Configure pod disruption budgets to ensure high availability during cluster maintenance.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: minio-pdb
namespace: minio-tenant
spec:
minAvailable: 75%
selector:
matchLabels:
app: minio
Next steps
- Configure MinIO backup and disaster recovery with automated snapshots and replication
- Implement MinIO security hardening with IAM policies and audit logging
- Setup Kubernetes Ingress NGINX with cert-manager for automated SSL certificates
- Configure Kubernetes network policies with Calico CNI for microsegmentation and security enforcement
- Monitor Kubernetes clusters with Prometheus and Grafana for container orchestration insights
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration
MINIO_OPERATOR_VERSION="v6.0.4"
MINIO_IMAGE="minio/minio:RELEASE.2024-01-16T16-07-38Z"
STORAGE_SIZE="${STORAGE_SIZE:-100Gi}"
MINIO_NAMESPACE="minio-tenant"
WORKER_NODES="${WORKER_NODES:-4}"
# Cleanup function
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
kubectl delete namespace ${MINIO_NAMESPACE} --ignore-not-found=true 2>/dev/null || true
kubectl delete -k "github.com/minio/operator?ref=${MINIO_OPERATOR_VERSION}" --ignore-not-found=true 2>/dev/null || true
rm -f /tmp/minio-*.yaml
exit 1
}
trap cleanup ERR
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -s, --storage-size Storage size per volume (default: 100Gi)"
echo " -n, --nodes Number of worker nodes (default: 4)"
echo " -h, --help Show this help message"
exit 1
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-s|--storage-size)
STORAGE_SIZE="$2"
shift 2
;;
-n|--nodes)
WORKER_NODES="$2"
shift 2
;;
-h|--help)
usage
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
usage
;;
esac
done
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian) PKG_MGR="apt"; PKG_INSTALL="apt install -y"; PKG_UPDATE="apt update" ;;
almalinux|rocky|centos|rhel|ol|fedora) PKG_MGR="dnf"; PKG_INSTALL="dnf install -y"; PKG_UPDATE="dnf check-update" || true ;;
amzn) PKG_MGR="yum"; PKG_INSTALL="yum install -y"; PKG_UPDATE="yum check-update" || true ;;
*) echo -e "${RED}Unsupported distro: $ID${NC}"; exit 1 ;;
esac
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
echo -e "${GREEN}[1/12] Checking prerequisites...${NC}"
# Check if running as root or with sudo
if [[ $EUID -eq 0 ]]; then
SUDO=""
else
SUDO="sudo"
fi
# Update package manager
echo -e "${YELLOW}Updating package manager...${NC}"
$SUDO $PKG_UPDATE
# Install required packages
echo -e "${YELLOW}Installing required packages...${NC}"
$SUDO $PKG_INSTALL curl wget
# Check kubectl
if ! command -v kubectl &> /dev/null; then
echo -e "${RED}kubectl is required but not installed${NC}"
exit 1
fi
# Check cluster connectivity
echo -e "${GREEN}[2/12] Verifying Kubernetes cluster connectivity...${NC}"
if ! kubectl cluster-info &> /dev/null; then
echo -e "${RED}Cannot connect to Kubernetes cluster${NC}"
exit 1
fi
# Check cluster admin permissions
echo -e "${GREEN}[3/12] Checking cluster admin permissions...${NC}"
if ! kubectl auth can-i "*" "*" --all-namespaces &> /dev/null; then
echo -e "${RED}Cluster admin permissions required${NC}"
exit 1
fi
# Check worker nodes
echo -e "${GREEN}[4/12] Verifying worker nodes...${NC}"
AVAILABLE_NODES=$(kubectl get nodes --no-headers | grep -v master | grep Ready | wc -l)
if [ "$AVAILABLE_NODES" -lt 3 ]; then
echo -e "${YELLOW}Warning: Less than 3 worker nodes available. HA setup may not work optimally${NC}"
fi
echo -e "${GREEN}[5/12] Installing MinIO Kubernetes operator...${NC}"
kubectl apply -k "github.com/minio/operator?ref=${MINIO_OPERATOR_VERSION}"
echo -e "${GREEN}[6/12] Waiting for operator installation...${NC}"
kubectl wait --for=condition=available --timeout=300s deployment/minio-operator -n minio-operator
kubectl wait --for=condition=available --timeout=300s deployment/console -n minio-operator
echo -e "${GREEN}[7/12] Verifying operator installation...${NC}"
kubectl get pods -n minio-operator
kubectl get crd | grep minio
echo -e "${GREEN}[8/12] Creating storage class for MinIO...${NC}"
cat > /tmp/minio-storage-class.yaml << 'EOF'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: minio-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
type: local
EOF
kubectl apply -f /tmp/minio-storage-class.yaml
echo -e "${GREEN}[9/12] Creating MinIO tenant namespace...${NC}"
kubectl create namespace ${MINIO_NAMESPACE} --dry-run=client -o yaml | kubectl apply -f -
echo -e "${GREEN}[10/12] Creating MinIO credentials secret...${NC}"
MINIO_ROOT_USER=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-16)
MINIO_ROOT_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-32)
kubectl create secret generic minio-creds-secret \
--from-literal=rootUser=${MINIO_ROOT_USER} \
--from-literal=rootPassword=${MINIO_ROOT_PASSWORD} \
-n ${MINIO_NAMESPACE} --dry-run=client -o yaml | kubectl apply -f -
echo -e "${GREEN}[11/12] Deploying MinIO tenant...${NC}"
cat > /tmp/minio-tenant.yaml << EOF
apiVersion: minio.min.io/v2
kind: Tenant
metadata:
name: minio-cluster
namespace: ${MINIO_NAMESPACE}
spec:
image: ${MINIO_IMAGE}
credsSecret:
name: minio-creds-secret
pools:
- servers: ${WORKER_NODES}
name: pool-0
volumesPerServer: 1
volumeClaimTemplate:
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: ${STORAGE_SIZE}
storageClassName: minio-storage
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
fsGroup: 1000
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 500m
memory: 1Gi
mountPath: /export
subPath: /data
requestAutoCert: false
EOF
kubectl apply -f /tmp/minio-tenant.yaml
echo -e "${GREEN}[12/12] Verifying MinIO deployment...${NC}"
echo -e "${YELLOW}Waiting for MinIO pods to be ready...${NC}"
kubectl wait --for=condition=ready --timeout=600s pods -l app=minio -n ${MINIO_NAMESPACE}
echo -e "${GREEN}MinIO installation completed successfully!${NC}"
echo
echo -e "${YELLOW}MinIO Cluster Information:${NC}"
echo "Namespace: ${MINIO_NAMESPACE}"
echo "Root User: ${MINIO_ROOT_USER}"
echo "Root Password: ${MINIO_ROOT_PASSWORD}"
echo
echo -e "${YELLOW}To access MinIO:${NC}"
echo "1. Port forward to MinIO service:"
echo " kubectl port-forward svc/minio-cluster-hl 9000:9000 -n ${MINIO_NAMESPACE}"
echo
echo "2. Access MinIO at: http://localhost:9000"
echo
echo -e "${YELLOW}To access MinIO Console:${NC}"
echo "1. Port forward to console service:"
echo " kubectl port-forward svc/minio-cluster-console 9001:9001 -n ${MINIO_NAMESPACE}"
echo
echo "2. Access Console at: http://localhost:9001"
echo
echo -e "${GREEN}Pods status:${NC}"
kubectl get pods -n ${MINIO_NAMESPACE}
echo
echo -e "${GREEN}Services:${NC}"
kubectl get svc -n ${MINIO_NAMESPACE}
# Cleanup temp files
rm -f /tmp/minio-*.yaml
echo -e "${GREEN}Installation completed successfully!${NC}"
Review the script before running. Execute with: bash install.sh