DEV Community

Python-T Point
Python-T Point

Posted on • Originally published at pythontpoint.in

☁️ Deploy MinIO on Kubernetes with Helm made easy

🚀 Helm Chart Basics — Why They Matter

deploy MinIO on Kubernetes with Helm

Helm turns a packaged chart into Kubernetes objects via templating and release management. Adding the official MinIO Helm repository and installing the chart looks like this:

📑 Table of Contents

  • 🚀 Helm Chart Basics — Why They Matter
  • ⚙️ Configuring MinIO — How to Customize
  • 📦 Persistent Volume — Storage
  • 🔐 Authentication — Secrets
  • 📡 Network Exposure — Making the Service Reachable
  • 📈 Monitoring & Scaling — Observability Metrics
  • 🟩 Final Thoughts
  • ❓ Frequently Asked Questions
  • Can I use an existing secret instead of the static keys defined in values.yaml?
  • How do I enable TLS for the MinIO endpoint?
  • Is it safe to run MinIO with the default access key in production?
  • 📚 References & Further Reading

⚙️ Configuring MinIO — How to Customize

Custom values control storage size, access keys, and high‑availability mode.

Create a values.yaml that overrides the defaults. Save it as minio-values.yaml:

# minio-values.yaml
accessKey: "minioadmin"
secretKey: "minioadmin123"
replicas: 4
persistence: enabled: true size: 20Gi storageClass: "standard"
service: type: LoadBalancer port: 9000
metrics: enabled: true serviceMonitor: enabled: true
Enter fullscreen mode Exit fullscreen mode

What this does:

  • accessKey / secretKey: static credentials injected as a secret; MinIO uses them for S3‑compatible authentication.
  • replicas: configures the StatefulSet to run four pods, enabling distributed erasure‑coding.
  • persistence.size: requests 20 Gi of storage per pod; the underlying PersistentVolume will be provisioned by the standard storage class.
  • service.type: exposes MinIO via a cloud‑provider LoadBalancer, making the endpoint reachable outside the cluster.
  • metrics.serviceMonitor: adds Prometheus annotations for automatic scraping.

Apply the customized chart:

$ helm upgrade -install minio-release minio/minio -f minio-values.yaml -namespace storage
Release "minio-release" has been upgraded.
Enter fullscreen mode Exit fullscreen mode

Why this, not the obvious alternative? Directly editing the chart’s templates/ would require a fork and create future merge conflicts; providing a values.yaml keeps the upstream chart intact while still tailoring the deployment.

📦 Persistent Volume — Storage

The chart creates a PersistentVolumeClaim per replica based on the persistence block. Example PVC after the release:

$ kubectl get pvc -n storage
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
minio-release-minio-0 Bound pvc-3b2a1c4e-8d5f-4a6b-9c8d-1e2f3a4b5c6d 20Gi RWO standard 2m
minio-release-minio-1 Bound pvc-4c5d6e7f-9a0b-1c2d-3e4f-5a6b7c8d9e0f 20Gi RWO standard 2m
...
Enter fullscreen mode Exit fullscreen mode

Each PVC is bound to a separate disk, ensuring data locality and fault isolation per pod. (Also read: ⚙️ Kubernetes RBAC vs ServiceAccount permissions — which provides tighter security?)

🔐 Authentication — Secrets

Helm stores the credentials as a Kubernetes Secret. Verify its contents (base64‑decoded for readability):

$ kubectl get secret minio-release-minio -n storage -o jsonpath="{.data.accesskey}" | base64 -decode
minioadmin
$ kubectl get secret minio-release-minio -n storage -o jsonpath="{.data.secretkey}" | base64 -decode
minioadmin123
Enter fullscreen mode Exit fullscreen mode

Storing credentials in a secret avoids hard‑coding them in pod specs; the secret is mounted read‑only into each container.

Key point: Value overrides let you align MinIO’s storage, security, and networking with the target environment without modifying chart source.


📡 Network Exposure — Making the Service Reachable

A Service of type LoadBalancer routes external traffic to MinIO pods.

The chart’s service block creates a Service object. Inspect it: (Also read: ☁️ Deploy FastAPI on Azure App Service vs AKS — which one should you actually use?)

$ kubectl get svc minio-release-minio -n storage -o yaml
apiVersion: v1
kind: Service
metadata: name: minio-release-minio namespace: storage
spec: type: LoadBalancer ports: - port: 9000 targetPort: 9000 protocol: TCP selector: app.kubernetes.io/name: minio app.kubernetes.io/instance: minio-release
Enter fullscreen mode Exit fullscreen mode

What this does:

  • type: LoadBalancer: asks the cloud provider to provision an external IP.
  • port: 9000: exposes the S3 API on the standard port.
  • selector: ties the Service to the MinIO pods via labels.

After provisioning, the external IP appears: (Also read: 🚀 Terraform deploy for Python Flask and Docker made easy)

$ kubectl get svc minio-release-minio -n storage
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
minio-release-minio LoadBalancer 10.96.123.45 34.212.56.78 9000:30678/TCP 3m
Enter fullscreen mode Exit fullscreen mode

Why this, not the obvious alternative? Using a LoadBalancer avoids the need for a NodePort and manual firewall rules; the cloud controller automatically updates the external IP when the service changes.

Key point: A properly typed Service abstracts the underlying network topology, letting clients reach MinIO via a stable endpoint. (More onPythonTPoint tutorials)


📈 Monitoring & Scaling — Observability Metrics

Prometheus annotations enable scraping, and the StatefulSet can be scaled horizontally for performance.

Metrics are enabled in values.yaml (under metrics.enabled). The chart adds these annotations to the pod template:

# snippet from the rendered pod manifest
metadata: annotations: prometheus.io/scrape: "true" prometheus.io/port: "9000"
Enter fullscreen mode Exit fullscreen mode

Verify that Prometheus discovers the endpoint:

$ kubectl get endpoints -n monitoring -l app=minio-release-minio
NAME ENDPOINTS AGE
minio-release-minio 10.244.0.5:9000,10.244.1.6:9000,10.244.2.7:9000 1m
Enter fullscreen mode Exit fullscreen mode

To increase capacity, scale the StatefulSet:

$ kubectl scale statefulset minio-release-minio -replicas=6 -n storage
statefulset.apps/minio-release-minio scaled
Enter fullscreen mode Exit fullscreen mode

After scaling, new pods appear:

$ kubectl get pods -n storage -l app=minio
NAME READY STATUS RESTARTS AGE
minio-release-minio-0 1/1 Running 0 5m
minio-release-minio-1 1/1 Running 0 5m
minio-release-minio-2 1/1 Running 0 5m
minio-release-minio-3 1/1 Running 0 5m
minio-release-minio-4 1/1 Running 0 30s
minio-release-minio-5 1/1 Running 0 30s
Enter fullscreen mode Exit fullscreen mode

Why this, not the obvious alternative? Directly editing the StatefulSet spec would bypass Helm’s release tracking, making future upgrades error‑prone; kubectl scale respects the Helm release while adjusting runtime capacity.

Helm keeps the desired state declarative, while kubectl scale lets you react to load without breaking that declaration.


🟩 Final Thoughts

Deploying MinIO on Kubernetes with Helm combines packaged best practices with a single source of truth for configuration. The Helm release stores the full manifest, enabling versioned rollbacks, while the underlying Kubernetes primitives—StatefulSets, Services, PersistentVolumeClaims—handle data durability and network exposure. By customizing values.yaml you align storage size, replica count, and security to the target workload without touching chart templates.

For production, consider integrating external secret management, enabling TLS via an Ingress controller, and adding Prometheus alerts for bucket‑level metrics. The same Helm‑driven workflow scales cleanly across clusters, providing a reliable foundation for any S3‑compatible storage layer.

❓ Frequently Asked Questions

Can I use an existing secret instead of the static keys defined in values.yaml?

Yes. Set existingSecret in values.yaml to reference a pre‑created secret; the chart will skip generating its own and mount the provided credentials.

How do I enable TLS for the MinIO endpoint?

Configure the service.tls block in values.yaml with a secret containing tls.crt and tls.key. The chart will create an Ingress resource that terminates TLS.

Is it safe to run MinIO with the default access key in production?

No. The default credentials are for testing only. Always override accessKey and secretKey with strong, unique values before exposing the service.

💡 Want to practise this hands-on? DigitalOcean gives new accounts $200 free credit for 60 days — enough to spin up a full Linux/Docker/Kubernetes environment at no cost.

📚 Recommended reading: Best DevOps & cloud books on Amazon — from Linux fundamentals to Kubernetes in production, curated for working engineers.

📚 References & Further Reading

  • Official MinIO documentation — comprehensive guide to deployment and configuration: min.io/docs/min.io
  • Kubernetes official docs — details on StatefulSets, Services, and Secrets: kubernetes.io
  • Helm official documentation — chart packaging, templating, and release management: helm.sh

Top comments (0)