This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch release/v1.2
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/release/v1.2 by this push:
     new ba12d74392 feat(deploy, v1.2): replace MinIO with RustFS as the 
default object store (#8555)
ba12d74392 is described below

commit ba12d7439249e328bb1b1d758fcc110f046afa73
Author: Xuan Gu <[email protected]>
AuthorDate: Fri Sep 18 02:34:24 2026 +0000

    feat(deploy, v1.2): replace MinIO with RustFS as the default object store 
(#8555)
    
    ### What changes were proposed in this PR?
    
    Backport of #8542 to `release/v1.2`. Fresh 1.2 deployments are broken by
    the MinIO image removal: the released compose tarball fails at image
    pull, the K8s lakekeeper-init job dies downloading `mc` from `dl.min.io`
    (410 Gone), and CI on this branch fails at image pull.
    
    Main adaptations for v1.2:
    
    - The chart has no `_helpers.tpl` and no external-S3 support, so
    templates reference the RustFS subchart's service/Secret directly,
    including two deployment templates that hardcode the MinIO secret on
    v1.2.
    - lakekeeper-init creates the bucket from an `rc` init container instead
    of downloading `mc` at runtime; CI uses the preinstalled AWS CLI.
    - Test files are adjusted to v1.2's package layout; the
    `StagedFileCleanupJob` change does not apply here.
    
    Migration note: volume/claim names change (`minio_data` ->
    `rustfs_data`, `minio-data-pvc` -> `rustfs-data-pvc`); no in-place data
    migration for now.
    
    ### Any related issues, documentation, discussions?
    
    Discussion in #3998.
    Original change: #8542.
    
    ### How was this PR tested?
    
    Compose and Helm render cleanly with no minio left. Tested the
    single-node deployment.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Fable 5 (Claude Code)
    
    ---------
    
    Co-authored-by: Ian Maxon <[email protected]>
    Co-authored-by: Claude Fable 5 <[email protected]>
    Co-authored-by: Xinyuan Lin <[email protected]>
    Co-authored-by: Meng Wang <[email protected]>
---
 .github/workflows/build.yml                        | 28 +++++---
 amber/LICENSE-binary-python                        |  3 +-
 .../texera/web/service/WorkflowService.scala       |  2 +-
 .../packaging/test_state_materialization_e2e.py    |  2 +-
 .../core/storage/iceberg/test_iceberg_document.py  |  4 +-
 .../pytexera/storage/test_large_binary_manager.py  |  4 +-
 bin/bootstrap-lakekeeper.sh                        |  2 +-
 bin/k8s/Chart.yaml                                 |  6 +-
 bin/k8s/templates/external-names.yaml              |  6 +-
 bin/k8s/templates/file-service-deployment.yaml     | 10 +--
 bin/k8s/templates/gateway-routes.yaml              | 10 +--
 bin/k8s/templates/gateway.yaml                     | 12 ++--
 bin/k8s/templates/lakekeeper-init-job.yaml         | 63 ++++++++++++-----
 ...io-persistence.yaml => rustfs-persistence.yaml} |  8 +--
 ...workflow-computing-unit-manager-deployment.yaml | 10 +--
 bin/k8s/values-development.yaml                    | 52 +++++++++-----
 bin/k8s/values.yaml                                | 67 +++++++++++++-----
 bin/local-dev/README.md                            |  2 +-
 bin/single-node/.env                               | 10 +--
 bin/single-node/README.md                          |  4 +-
 bin/single-node/docker-compose.yml                 | 65 ++++++++++--------
 build.sbt                                          |  1 +
 common/config/src/main/resources/storage.conf      |  2 +-
 common/workflow-core/build.sbt                     |  5 +-
 .../texera/service/util/S3StorageClient.scala      |  8 +--
 .../texera/service/util/RustFSContainer.scala      | 80 ++++++++++++++++++++++
 .../texera/service/util/S3StorageTestBase.scala    | 21 +++---
 .../guide-for-developers.md                        |  6 +-
 docs/getting-started/installing-using-docker.md    |  4 +-
 docs/getting-started/run-on-kubernetes.md          |  4 +-
 docs/overview.md                                   |  2 +-
 docs/tutorials/guide-to-launch-lakekeeper.md       |  6 +-
 file-service/build.sbt                             |  3 +-
 file-service/src/main/resources/docker-compose.yml | 25 ++++---
 .../{minio-config.yml => rustfs-config.yml}        | 23 ++++---
 .../org/apache/texera/service/MockLakeFS.scala     | 60 ++++++++--------
 36 files changed, 401 insertions(+), 219 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5e3ada2c31..2d468dac51 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -399,17 +399,19 @@ jobs:
         run: psql -h localhost -U postgres -v DB_NAME=texera_db_for_test_cases 
-f sql/texera_ddl.sql
         env:
           PGPASSWORD: postgres
-      - name: Start MinIO
+      - name: Start RustFS
         run: |
-          docker run -d --name minio --network host \
-            -e MINIO_ROOT_USER=texera_minio \
-            -e MINIO_ROOT_PASSWORD=password \
-            minio/minio:RELEASE.2025-02-28T09-55-16Z server /data
-          for i in $(seq 1 3); do
-            curl -sf http://localhost:9000/minio/health/live && break
-            echo "Waiting for MinIO... (attempt $i)"
+          docker run -d --name rustfs --network host \
+            -e RUSTFS_ACCESS_KEY=texera_rustfs \
+            -e RUSTFS_SECRET_KEY=password \
+            -e RUSTFS_REGION=us-west-2 \
+            rustfs/rustfs:1.0.0-rc.6
+          for i in $(seq 1 15); do
+            curl -sf http://localhost:9000/health && break
+            echo "Waiting for RustFS... (attempt $i)"
             sleep 1
           done
+          curl -sf http://localhost:9000/health
       - name: Start Lakekeeper
         env:
           LAKEKEEPER__PG_DATABASE_URL_READ: 
postgres://postgres:postgres@localhost:5432/texera_lakekeeper
@@ -461,9 +463,13 @@ jobs:
           LAKEKEEPER_BASE=${REST_URI%/catalog}
           LAKEKEEPER_BASE=${LAKEKEEPER_BASE%/}
 
-          docker run --rm --network host --entrypoint sh minio/mc -c \
-            "mc alias set minio $S3_ENDPOINT $S3_USERNAME $S3_PASSWORD && \
-             mc mb --ignore-existing minio/$S3_BUCKET"
+          # Bucket creation goes through the AWS CLI, which is preinstalled on
+          # the runner image. CreateBucket on an existing bucket is a no-op on
+          # RustFS, which keeps re-runs idempotent.
+          AWS_ACCESS_KEY_ID="$S3_USERNAME" \
+          AWS_SECRET_ACCESS_KEY="$S3_PASSWORD" \
+          AWS_DEFAULT_REGION="$S3_REGION" \
+            aws s3api create-bucket --bucket "$S3_BUCKET" --endpoint-url 
"$S3_ENDPOINT"
           curl -sf -X POST -H 'Content-Type: application/json' \
             -d 
'{"project-id":"00000000-0000-0000-0000-000000000000","project-name":"default"}'
 \
             "$LAKEKEEPER_BASE/management/v1/project" || true
diff --git a/amber/LICENSE-binary-python b/amber/LICENSE-binary-python
index a571897d52..11fd33977a 100644
--- a/amber/LICENSE-binary-python
+++ b/amber/LICENSE-binary-python
@@ -300,6 +300,7 @@ Dependencies under the BSD 3-Clause License
 Python packages:
   - cached-property==1.5.2
   - click==8.4.2
+  - cloudpickle==3.1.2
   - contourpy==1.3.3
   - cycler==0.12.1
   - fsspec==2025.9.0
@@ -365,7 +366,7 @@ Dependencies under the Python Software Foundation License
 Python packages:
   - aiohappyeyeballs==2.7.1
   - matplotlib==3.11.1
-  - typing-extensions==4.14.1
+  - typing-extensions==4.16.0
 
 
--------------------------------------------------------------------------------
 Dependencies under the MIT-CMU License
diff --git 
a/amber/src/main/scala/org/apache/texera/web/service/WorkflowService.scala 
b/amber/src/main/scala/org/apache/texera/web/service/WorkflowService.scala
index f6c67159ae..b1cc506b5d 100644
--- a/amber/src/main/scala/org/apache/texera/web/service/WorkflowService.scala
+++ b/amber/src/main/scala/org/apache/texera/web/service/WorkflowService.scala
@@ -349,7 +349,7 @@ class WorkflowService(
     *  2. Clears URI references from the execution registry
     *  3. Safely clears all result and console message documents
     *  4. Expires Iceberg snapshots for runtime statistics
-    *  5. Deletes this execution's large binaries from MinIO
+    *  5. Deletes this execution's large binaries from the object store
     *
     * @param eid The execution identity to clean up resources for
     */
diff --git 
a/amber/src/test/python/core/architecture/packaging/test_state_materialization_e2e.py
 
b/amber/src/test/python/core/architecture/packaging/test_state_materialization_e2e.py
index cfc4f7f676..93508b6e9a 100644
--- 
a/amber/src/test/python/core/architecture/packaging/test_state_materialization_e2e.py
+++ 
b/amber/src/test/python/core/architecture/packaging/test_state_materialization_e2e.py
@@ -80,7 +80,7 @@ _WAREHOUSE_DIR = 
tempfile.mkdtemp(prefix="texera-state-e2e-warehouse-")
 @pytest.fixture(scope="module", autouse=True)
 def sqlite_iceberg_catalog():
     """Inject a sqlite-backed SqlCatalog so the test runs without external
-    iceberg infra (postgres/minio).
+    iceberg infra (postgres/rustfs).
 
     Module-scoped so all tests in this file share one warehouse, and so
     namespace creation only happens once. We save/restore the original
diff --git 
a/amber/src/test/python/core/storage/iceberg/test_iceberg_document.py 
b/amber/src/test/python/core/storage/iceberg/test_iceberg_document.py
index 5e61b4ed68..0b264b4508 100644
--- a/amber/src/test/python/core/storage/iceberg/test_iceberg_document.py
+++ b/amber/src/test/python/core/storage/iceberg/test_iceberg_document.py
@@ -55,8 +55,8 @@ StorageConfig.initialize(
     commit_batch_size=4096,
     s3_endpoint="http://localhost:9000";,
     s3_region="us-east-1",
-    s3_auth_username="minioadmin",
-    s3_auth_password="minioadmin",
+    s3_auth_username="rustfsadmin",
+    s3_auth_password="rustfsadmin",
     s3_large_binaries_base_uri="s3://texera-large-binaries/objects/0/",
 )
 
diff --git 
a/amber/src/test/python/pytexera/storage/test_large_binary_manager.py 
b/amber/src/test/python/pytexera/storage/test_large_binary_manager.py
index e1da5eee18..ae24df206a 100644
--- a/amber/src/test/python/pytexera/storage/test_large_binary_manager.py
+++ b/amber/src/test/python/pytexera/storage/test_large_binary_manager.py
@@ -46,8 +46,8 @@ def _init_storage_config():
             commit_batch_size=1000,
             s3_endpoint="http://localhost:9000";,
             s3_region="us-east-1",
-            s3_auth_username="minioadmin",
-            s3_auth_password="minioadmin",
+            s3_auth_username="rustfsadmin",
+            s3_auth_password="rustfsadmin",
             s3_large_binaries_base_uri=TEST_BASE_URI,
         )
 
diff --git a/bin/bootstrap-lakekeeper.sh b/bin/bootstrap-lakekeeper.sh
index 17452e9f7d..3fd9f1d6a5 100755
--- a/bin/bootstrap-lakekeeper.sh
+++ b/bin/bootstrap-lakekeeper.sh
@@ -42,7 +42,7 @@ 
STORAGE_ICEBERG_CATALOG_REST_WAREHOUSE_NAME="${STORAGE_ICEBERG_CATALOG_REST_WARE
 
STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET="${STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET:-texera-iceberg}"
 STORAGE_S3_REGION="${STORAGE_S3_REGION:-us-west-2}"
 STORAGE_S3_ENDPOINT="${STORAGE_S3_ENDPOINT:-http://localhost:9000}";
-STORAGE_S3_AUTH_USERNAME="${STORAGE_S3_AUTH_USERNAME:-texera_minio}"
+STORAGE_S3_AUTH_USERNAME="${STORAGE_S3_AUTH_USERNAME:-texera_rustfs}"
 STORAGE_S3_AUTH_PASSWORD="${STORAGE_S3_AUTH_PASSWORD:-password}"
 
 # Lakekeeper binary — defaults to `lakekeeper` on $PATH (e.g. after
diff --git a/bin/k8s/Chart.yaml b/bin/k8s/Chart.yaml
index 548d9e531e..5bd5f1fa98 100644
--- a/bin/k8s/Chart.yaml
+++ b/bin/k8s/Chart.yaml
@@ -46,9 +46,9 @@ dependencies:
     version: 16.5.6
     repository: https://charts.bitnami.com/bitnami
 
-  - name: minio
-    version: 15.0.7
-    repository: https://charts.bitnami.com/bitnami
+  - name: rustfs
+    version: 1.0.0-rc.6
+    repository: https://charts.rustfs.com
 
   - name: lakefs
     version: 1.8.1
diff --git a/bin/k8s/templates/external-names.yaml 
b/bin/k8s/templates/external-names.yaml
index c79ce40f33..0e180bd37b 100644
--- a/bin/k8s/templates/external-names.yaml
+++ b/bin/k8s/templates/external-names.yaml
@@ -74,11 +74,11 @@ to access services in the main namespace using the same 
service names.
 ) | nindent 0 }}
 
 ---
-{{/* MinIO ExternalName */}}
+{{/* RustFS ExternalName */}}
 {{- include "external-name-service" (dict 
-  "name" (printf "%s-minio" .Release.Name)
+  "name" (printf "%s-rustfs-svc" .Release.Name)
   "namespace" $workflowComputingUnitPoolNamespace
-  "externalName" (printf "%s-minio.%s.svc.cluster.local" .Release.Name 
$namespace)
+  "externalName" (printf "%s-rustfs-svc.%s.svc.cluster.local" .Release.Name 
$namespace)
 ) | nindent 0 }}
 
 ---
diff --git a/bin/k8s/templates/file-service-deployment.yaml 
b/bin/k8s/templates/file-service-deployment.yaml
index 6c9a4041f9..f56085737a 100644
--- a/bin/k8s/templates/file-service-deployment.yaml
+++ b/bin/k8s/templates/file-service-deployment.yaml
@@ -41,17 +41,17 @@ spec:
           env:
             # LakeFS & S3 Access
             - name: STORAGE_S3_ENDPOINT
-              value: http://{{ .Release.Name }}-minio:9000
+              value: http://{{ .Release.Name }}-rustfs-svc:9000
             - name: STORAGE_S3_AUTH_USERNAME
               valueFrom:
                 secretKeyRef:
-                  name: {{ .Release.Name }}-minio
-                  key: root-user
+                  name: {{ .Release.Name }}-rustfs-secret
+                  key: RUSTFS_ACCESS_KEY
             - name: STORAGE_S3_AUTH_PASSWORD
               valueFrom:
                 secretKeyRef:
-                  name: {{ .Release.Name }}-minio
-                  key: root-password
+                  name: {{ .Release.Name }}-rustfs-secret
+                  key: RUSTFS_SECRET_KEY
             - name: STORAGE_LAKEFS_ENDPOINT
               value: http://{{ .Release.Name }}-lakefs:8000/api/v1
             - name: STORAGE_LAKEFS_AUTH_USERNAME
diff --git a/bin/k8s/templates/gateway-routes.yaml 
b/bin/k8s/templates/gateway-routes.yaml
index 935d38bb07..ace2fc5ee7 100644
--- a/bin/k8s/templates/gateway-routes.yaml
+++ b/bin/k8s/templates/gateway-routes.yaml
@@ -159,24 +159,24 @@ spec:
           port: 3001
 {{- end }}
 ---
-# MinIO Route
-{{- if .Values.minio.gateway.enabled }}
+# RustFS Route
+{{- if .Values.rustfs.gateway.enabled }}
 apiVersion: gateway.networking.k8s.io/v1
 kind: HTTPRoute
 metadata:
-  name: texera-minio-route
+  name: texera-rustfs-route
   namespace: {{ .Release.Namespace }}
 spec:
   parentRefs:
     - name: {{ .Release.Name }}-gateway
   hostnames:
-    - {{ .Values.minio.gateway.hostname }}
+    - {{ .Values.rustfs.gateway.hostname }}
   rules:
     - matches:
         - path:
             type: PathPrefix
             value: /
       backendRefs:
-        - name: {{ .Release.Name }}-minio
+        - name: {{ .Release.Name }}-rustfs-svc
           port: 9000
 {{- end }}
diff --git a/bin/k8s/templates/gateway.yaml b/bin/k8s/templates/gateway.yaml
index 868439a673..dc79e39d6d 100644
--- a/bin/k8s/templates/gateway.yaml
+++ b/bin/k8s/templates/gateway.yaml
@@ -59,23 +59,23 @@ spec:
         mode: Terminate
         certificateRefs:
           - name: {{ (index .Values "gatewayConfig" "tlsSecretName") | default 
(printf "%s-cert" .Release.Name) }}
-    {{- if .Values.minio.gateway.enabled }}
-    - name: minio-http
+    {{- if .Values.rustfs.gateway.enabled }}
+    - name: rustfs-http
       protocol: HTTP
       port: 80
-      hostname: {{ .Values.minio.gateway.hostname }}
+      hostname: {{ .Values.rustfs.gateway.hostname }}
       allowedRoutes:
         namespaces:
           from: Same
-    - name: minio-https
+    - name: rustfs-https
       protocol: HTTPS
       port: 443
-      hostname: {{ .Values.minio.gateway.hostname }}
+      hostname: {{ .Values.rustfs.gateway.hostname }}
       allowedRoutes:
         namespaces:
           from: Same
       tls:
         mode: Terminate
         certificateRefs:
-          - name: {{ .Values.minio.gateway.tlsSecretName | default (printf 
"%s-minio-tls" .Release.Name) }}
+          - name: {{ .Values.rustfs.gateway.tlsSecretName | default (printf 
"%s-rustfs-tls" .Release.Name) }}
     {{- end }}
diff --git a/bin/k8s/templates/lakekeeper-init-job.yaml 
b/bin/k8s/templates/lakekeeper-init-job.yaml
index 0a3540b605..161638c896 100644
--- a/bin/k8s/templates/lakekeeper-init-job.yaml
+++ b/bin/k8s/templates/lakekeeper-init-job.yaml
@@ -28,22 +28,62 @@ spec:
       name: {{ .Release.Name }}-lakekeeper-init
     spec:
       restartPolicy: Never
+      # Creates the Iceberg warehouse bucket before the warehouse is 
registered.
+      # `rc` is RustFS's S3 client; using its image keeps the client out of the
+      # main container, which would otherwise have to fetch a binary at 
runtime.
+      initContainers:
+        - name: create-bucket
+          image: {{ .Values.lakekeeperInit.bucketClientImage }}
+          env:
+            - name: STORAGE_S3_ENDPOINT
+              value: http://{{ .Release.Name }}-rustfs-svc:9000
+            - name: STORAGE_S3_AUTH_USERNAME
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-rustfs-secret
+                  key: RUSTFS_ACCESS_KEY
+            - name: STORAGE_S3_AUTH_PASSWORD
+              valueFrom:
+                secretKeyRef:
+                  name: {{ .Release.Name }}-rustfs-secret
+                  key: RUSTFS_SECRET_KEY
+            - name: STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET
+              value: {{ .Values.lakekeeperInit.warehouse.s3Bucket | quote }}
+            - name: STORAGE_ICEBERG_CATALOG_REST_REGION
+              value: {{ .Values.lakekeeperInit.warehouse.region | quote }}
+          command:
+            - /bin/sh
+            - -c
+            - |
+              set -e
+
+              echo "Waiting for the object store at ${STORAGE_S3_ENDPOINT}..."
+              until rc alias set local "${STORAGE_S3_ENDPOINT}" \
+                    "${STORAGE_S3_AUTH_USERNAME}" 
"${STORAGE_S3_AUTH_PASSWORD}" \
+                    --region "${STORAGE_ICEBERG_CATALOG_REST_REGION}" \
+                    --bucket-lookup path > /dev/null 2>&1; do
+                sleep 3
+              done
+
+              echo "Initializing object-store bucket 
'${STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET}'..."
+              rc bucket create --ignore-existing 
"local/${STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET}"
+              echo "Bucket '${STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET}' is 
ready."
       containers:
         - name: lakekeeper-init
           image: alpine:3.19
           env:
             - name: STORAGE_S3_ENDPOINT
-              value: http://{{ .Release.Name }}-minio:9000
+              value: http://{{ .Release.Name }}-rustfs-svc:9000
             - name: STORAGE_S3_AUTH_USERNAME
               valueFrom:
                 secretKeyRef:
-                  name: {{ .Release.Name }}-minio
-                  key: root-user
+                  name: {{ .Release.Name }}-rustfs-secret
+                  key: RUSTFS_ACCESS_KEY
             - name: STORAGE_S3_AUTH_PASSWORD
               valueFrom:
                 secretKeyRef:
-                  name: {{ .Release.Name }}-minio
-                  key: root-password
+                  name: {{ .Release.Name }}-rustfs-secret
+                  key: RUSTFS_SECRET_KEY
             - name: STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET
               value: {{ .Values.lakekeeperInit.warehouse.s3Bucket | quote }}
             - name: STORAGE_ICEBERG_CATALOG_REST_REGION
@@ -62,9 +102,7 @@ spec:
             - |
               set -e
 
-              apk add --no-cache curl ca-certificates wget
-              wget -q https://dl.min.io/client/mc/release/linux-amd64/mc -O 
/usr/local/bin/mc
-              chmod +x /usr/local/bin/mc
+              apk add --no-cache curl ca-certificates
 
               check_status() {
                 if [ "$1" -ge 200 ] && [ "$1" -lt 300 ]; then
@@ -85,14 +123,7 @@ spec:
                 sleep 3
               done
 
-              echo "Step 1: Initializing MinIO bucket 
'${STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET}'..."
-              mc alias set minio "${STORAGE_S3_ENDPOINT}" 
"${STORAGE_S3_AUTH_USERNAME}" "${STORAGE_S3_AUTH_PASSWORD}" || true
-              if mc ls minio/${STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET} > 
/dev/null 2>&1; then
-                echo "MinIO bucket '${STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET}' 
already exists."
-              else
-                mc mb minio/${STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET}
-                echo "MinIO bucket '${STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET}' 
created successfully."
-              fi
+              echo "Step 1: object-store bucket 
'${STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET}' was created by the create-bucket 
init container."
 
               echo "Step 2: Initializing default project..."
               
PROJECT_PAYLOAD="{\"project-id\":\"${LAKEKEEPER_PROJECT_ID}\",\"project-name\":\"${LAKEKEEPER_PROJECT_NAME}\"}"
diff --git a/bin/k8s/templates/minio-persistence.yaml 
b/bin/k8s/templates/rustfs-persistence.yaml
similarity index 92%
rename from bin/k8s/templates/minio-persistence.yaml
rename to bin/k8s/templates/rustfs-persistence.yaml
index 94d22e5e3a..173f93481d 100644
--- a/bin/k8s/templates/minio-persistence.yaml
+++ b/bin/k8s/templates/rustfs-persistence.yaml
@@ -17,11 +17,11 @@
 
 {{/* Define storage path configuration, please change it to your own path and 
make sure the path exists with the right permission*/}}
 {{/* This path only works for local-path storage class */}}
-{{- $hostBasePath := .Values.persistence.minioHostLocalPath }}
+{{- $hostBasePath := .Values.persistence.rustfsHostLocalPath }}
 
-{{- if .Values.minio.persistence.enabled }}
-{{- $name := "minio" }}
-{{- $persistence := .Values.minio.persistence }}
+{{- if .Values.rustfs.persistence.enabled }}
+{{- $name := "rustfs" }}
+{{- $persistence := .Values.rustfs.persistence }}
 {{- $volumeName := printf "%s-data-pv" $name }}
 {{- $claimName := printf "%s-data-pvc" $name }}
 {{- $storageClass := $persistence.storageClass | default "local-path" }}
diff --git a/bin/k8s/templates/workflow-computing-unit-manager-deployment.yaml 
b/bin/k8s/templates/workflow-computing-unit-manager-deployment.yaml
index 7a0185cd46..299297f133 100644
--- a/bin/k8s/templates/workflow-computing-unit-manager-deployment.yaml
+++ b/bin/k8s/templates/workflow-computing-unit-manager-deployment.yaml
@@ -81,17 +81,17 @@ spec:
               value: http://{{ .Values.fileService.name 
}}-svc:9092/api/dataset/did/upload
             # S3 Access (for R UDF large binary support)
             - name: STORAGE_S3_ENDPOINT
-              value: http://{{ .Release.Name }}-minio:9000
+              value: http://{{ .Release.Name }}-rustfs-svc:9000
             - name: STORAGE_S3_AUTH_USERNAME
               valueFrom:
                 secretKeyRef:
-                  name: {{ .Release.Name }}-minio
-                  key: root-user
+                  name: {{ .Release.Name }}-rustfs-secret
+                  key: RUSTFS_ACCESS_KEY
             - name: STORAGE_S3_AUTH_PASSWORD
               valueFrom:
                 secretKeyRef:
-                  name: {{ .Release.Name }}-minio
-                  key: root-password
+                  name: {{ .Release.Name }}-rustfs-secret
+                  key: RUSTFS_SECRET_KEY
             # LakeFS Access (should be removed in production environment)
             - name: STORAGE_LAKEFS_ENDPOINT
               value: http://{{ .Release.Name }}-lakefs.{{ .Release.Namespace 
}}:8000/api/v1
diff --git a/bin/k8s/values-development.yaml b/bin/k8s/values-development.yaml
index aa64d6ecbc..db82a3b417 100644
--- a/bin/k8s/values-development.yaml
+++ b/bin/k8s/values-development.yaml
@@ -22,7 +22,7 @@ texera:
   imageTag: latest
 
 global:
-  # Required by Bitnami sub-charts (postgresql, minio) to allow custom images
+  # Required by the Bitnami postgresql sub-chart to allow custom images
   security:
     allowInsecureImages: true
 
@@ -34,10 +34,10 @@ global:
 #   - false: PVCs will remain after uninstall to preserve the data
 persistence:
   removeAfterUninstall: true
-  minioHostLocalPath: ""
+  rustfsHostLocalPath: ""
   postgresqlHostLocalPath: ""
 
-# Part 1: the configuration of Postgres, Minio and LakeFS
+# Part 1: the configuration of Postgres, RustFS and LakeFS
 postgresql:
   image:
     repository: groonga/pgroonga
@@ -68,34 +68,50 @@ postgresql:
     initdb:
       scriptsConfigMap: "postgresql-init-script"
 
-minio:
-  mode: standalone
+rustfs:
+  replicaCount: 1
+  drivesPerNode: 1
+  mode:
+    standalone:
+      enabled: true
+      existingClaim:
+        dataClaim: "rustfs-data-pvc"
+    distributed:
+      enabled: false
   image:
-    repository: bitnamilegacy/minio
-    tag: 2025.3.12-debian-12-r0
+    rustfs:
+      repository: rustfs/rustfs
+      tag: 1.0.0-rc.6
   resources:
     requests:
       memory: "256Mi"
     limits:
       memory: "256Mi"
+  ingress:
+    enabled: false
   gateway:
     enabled: false
-    hostname: "" # the url for the minio, e.g. "minio.example.com"
-    tlsSecretName: "" # e.g. "minio-tls-secret"
-  auth:
-    rootUser: texera_minio
-    rootPassword: password
+    hostname: "" # the url for the object store, e.g. "rustfs.example.com"
+    tlsSecretName: "" # e.g. "rustfs-tls-secret"
+  secret:
+    rustfs:
+      access_key: texera_rustfs
+      secret_key: password
+  config:
+    rustfs:
+      region: us-west-2
+      obs_log_directory: ""
+      log_level: warn
   service:
-    # In production, use ClusterIP to avoid exposing the minio to the internet
+    # In production, use ClusterIP to avoid exposing the object store to the 
internet
     # type: ClusterIP
     type: NodePort
-    nodePorts:
-      api: 31000
+    endpoint:
+      nodePort: 31000
   persistence:
     enabled: true
     size: 20Gi
     storageClass: local-path
-    existingClaim: "minio-data-pvc"
 
 lakefs:
   secrets:
@@ -113,12 +129,12 @@ lakefs:
     blockstore:
       type: s3
       s3:
-        endpoint: http://texera-minio:9000
+        endpoint: http://texera-rustfs-svc:9000
         pre_signed_expiry: 15m
         pre_signed_endpoint: http://localhost:31000
         force_path_style: true
         credentials:
-          access_key_id: texera_minio
+          access_key_id: texera_rustfs
           secret_access_key: password
 
 # Part2: configurations of Texera-related micro services
diff --git a/bin/k8s/values.yaml b/bin/k8s/values.yaml
index 313930d10a..d7faf7b946 100644
--- a/bin/k8s/values.yaml
+++ b/bin/k8s/values.yaml
@@ -22,7 +22,7 @@ texera:
   imageTag: 1.2.0-incubating
 
 global:
-  # Required by Bitnami sub-charts (postgresql, minio) to allow custom images
+  # Required by the Bitnami postgresql sub-chart to allow custom images
   security:
     allowInsecureImages: true
 
@@ -34,10 +34,10 @@ global:
 #   - false: PVCs will remain after uninstall to preserve the data
 persistence:
   removeAfterUninstall: true
-  minioHostLocalPath: ""
+  rustfsHostLocalPath: ""
   postgresqlHostLocalPath: ""
 
-# Part 1: the configuration of Postgres, Minio and LakeFS
+# Part 1: the configuration of Postgres, RustFS and LakeFS
 postgresql:
   image:
     repository: groonga/pgroonga
@@ -65,29 +65,56 @@ postgresql:
     initdb:
       scriptsConfigMap: "postgresql-init-script"
 
-minio:
-  mode: standalone
+rustfs:
+  # Single-node object store, matching the previous standalone MinIO. The
+  # upstream chart defaults to a 4-replica distributed cluster; Texera's
+  # in-cluster store is a convenience for single-node installs, so keep it to
+  # one pod and one drive.
+  replicaCount: 1
+  drivesPerNode: 1
+  mode:
+    standalone:
+      enabled: true
+      existingClaim:
+        dataClaim: "rustfs-data-pvc"
+    distributed:
+      enabled: false
   image:
-    repository: bitnamilegacy/minio
-    tag: 2025.3.12-debian-12-r0
+    rustfs:
+      repository: rustfs/rustfs
+      tag: 1.0.0-rc.6
+  # Texera fronts the object store with Envoy Gateway (the `gateway` block
+  # below), not with an Ingress -- the upstream chart turns one on by default.
+  ingress:
+    enabled: false
   gateway:
     enabled: false
-    hostname: "" # the url for the minio, e.g. "minio.example.com"
-    tlsSecretName: "" # e.g. "minio-tls-secret"
-  auth:
-    rootUser: texera_minio
-    rootPassword: password
+    hostname: "" # the url for the object store, e.g. "rustfs.example.com"
+    tlsSecretName: "" # e.g. "rustfs-tls-secret"
+  secret:
+    rustfs:
+      access_key: texera_rustfs
+      secret_key: password
+  config:
+    rustfs:
+      # Must match the lakefs/lakekeeperInit region: it is part of the SigV4 
scope.
+      region: us-west-2
+      # Empty => log to stdout (`kubectl logs`) instead of a log PVC.
+      obs_log_directory: ""
+      log_level: warn
   service:
-    # In production, use ClusterIP to avoid exposing the minio to the internet
+    # In production, use ClusterIP to avoid exposing the object store to the 
internet
     # type: ClusterIP
     type: NodePort
-    nodePorts:
-      api: 31000
+    endpoint:
+      nodePort: 31000
+  # The chart's own PVC template is unused here: Texera provisions the claim
+  # itself (templates/rustfs-persistence.yaml) so that the local-path
+  # host directory stays under persistence.rustfsHostLocalPath.
   persistence:
     enabled: true
     size: 20Gi
     storageClass: local-path
-    existingClaim: "minio-data-pvc"
 
 lakefs:
   secrets:
@@ -105,12 +132,12 @@ lakefs:
     blockstore:
       type: s3
       s3:
-        endpoint: http://texera-minio:9000
+        endpoint: http://texera-rustfs-svc:9000
         pre_signed_expiry: 15m
         pre_signed_endpoint: http://localhost:31000
         force_path_style: true
         credentials:
-          access_key_id: texera_minio
+          access_key_id: texera_rustfs
           secret_access_key: password
 
 lakekeeper:
@@ -137,6 +164,10 @@ lakekeeper:
 
 lakekeeperInit:
   enabled: true
+  # S3 client image used by the create-bucket init container. `rc` is RustFS's
+  # client; any image whose entrypoint is an `mc`-style client with
+  # `alias set` / `bucket create` would do.
+  bucketClientImage: rustfs/rc:v0.1.35
   defaultProject:
     id: "00000000-0000-0000-0000-000000000000"
     name: default
diff --git a/bin/local-dev/README.md b/bin/local-dev/README.md
index a560f9098b..56df4154fa 100644
--- a/bin/local-dev/README.md
+++ b/bin/local-dev/README.md
@@ -42,7 +42,7 @@ bin/local-dev/
 ├── main.sh                       shell engine — sbt builds, service 
lifecycle, port checks
 ├── tui.py                        Textual dashboard surfaced by 
`bin/local-dev.sh -i`
 ├── docker-compose.override.yml   overlay on top of 
bin/single-node/docker-compose.yml
-│                                 (host-LAN-IP MinIO endpoint, Lakekeeper 
warehouse, etc.)
+│                                 (host-LAN-IP RustFS endpoint, Lakekeeper 
warehouse, etc.)
 └── tests/
     ├── test_local_dev_sh.sh      bash smoke: license header, syntax, version, 
--help,
     │                             error-on-bad-input, regression guards
diff --git a/bin/single-node/.env b/bin/single-node/.env
index 555e14db7d..4a8ba397bf 100644
--- a/bin/single-node/.env
+++ b/bin/single-node/.env
@@ -18,7 +18,7 @@
 # Public host and ports exposed by the deployment
 TEXERA_HOST=http://localhost
 TEXERA_PORT=8080
-MINIO_PORT=9000
+RUSTFS_PORT=9000
 
 # Log level for all Texera services (valid values: ERROR, WARN, INFO, DEBUG)
 TEXERA_SERVICE_LOG_LEVEL=INFO
@@ -36,8 +36,8 @@ USER_SYS_ADMIN_PASSWORD=texera
 POSTGRES_USER=texera
 POSTGRES_PASSWORD=password
 
-# S3 (MinIO) credentials
-STORAGE_S3_AUTH_USERNAME=texera_minio
+# S3 (RustFS) credentials
+STORAGE_S3_AUTH_USERNAME=texera_rustfs
 STORAGE_S3_AUTH_PASSWORD=password
 
 # LakeFS server configuration
@@ -46,7 +46,7 @@ LAKEFS_INSTALLATION_ACCESS_KEY_ID=AKIAIOSFOLKFSSAMPLES
 LAKEFS_INSTALLATION_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
 LAKEFS_BLOCKSTORE_TYPE=s3
 LAKEFS_BLOCKSTORE_S3_FORCE_PATH_STYLE=true
-LAKEFS_BLOCKSTORE_S3_ENDPOINT=http://texera-minio:9000
+LAKEFS_BLOCKSTORE_S3_ENDPOINT=http://texera-rustfs:9000
 LAKEFS_AUTH_ENCRYPT_SECRET_KEY=random_string_for_lakefs
 LAKEFS_LOGGING_LEVEL=INFO
 LAKEFS_STATS_ENABLED=1
@@ -60,7 +60,7 @@ LAKEKEEPER__PG_ENCRYPTION_KEY=texera_key
 LAKEKEEPER_BASE_URI=http://texera-lakekeeper:8181
 
 # Texera storage endpoints
-STORAGE_S3_ENDPOINT=http://texera-minio:9000
+STORAGE_S3_ENDPOINT=http://texera-rustfs:9000
 STORAGE_S3_REGION=us-west-2
 STORAGE_LAKEFS_ENDPOINT=http://texera-lakefs:8000/api/v1
 
STORAGE_JDBC_URL=jdbc:postgresql://texera-postgres:5432/texera_db?currentSchema=texera_db,public
diff --git a/bin/single-node/README.md b/bin/single-node/README.md
index 62d4834297..77e0f63407 100644
--- a/bin/single-node/README.md
+++ b/bin/single-node/README.md
@@ -153,11 +153,11 @@ All changes below are to the `.env` file in the 
installation folder, unless othe
 ### Run Texera on other ports
 By default, Texera uses:
 - Port 8080 for its web service
-- Port 9000 for its MinIO storage service
+- Port 9000 for its RustFS storage service
 
 To change these ports, open the `.env` file and update the corresponding 
variables:
 - For the web service port (8080): change `TEXERA_PORT=8080` to your desired 
port, e.g., `TEXERA_PORT=8081`.
-- For the MinIO port (9000): change `MINIO_PORT=9000` to your desired port, 
e.g., `MINIO_PORT=9001`.
+- For the RustFS port (9000): change `RUSTFS_PORT=9000` to your desired port, 
e.g., `RUSTFS_PORT=9001`.
 
 ### Change the locations of Texera data
 By default, Docker manages Texera's data locations. To change them to your own 
locations:
diff --git a/bin/single-node/docker-compose.yml 
b/bin/single-node/docker-compose.yml
index 64247d9d66..52f3a89a0e 100644
--- a/bin/single-node/docker-compose.yml
+++ b/bin/single-node/docker-compose.yml
@@ -18,33 +18,43 @@
 name: texera-single-node
 services:
   # Part1: Specification of the storage services used by Texera
-  # MinIO is an S3-compatible object storage used to store datasets and files.
-  minio:
-    image: minio/minio:RELEASE.2025-02-28T09-55-16Z
-    container_name: texera-minio
+  # RustFS is an S3-compatible object storage used to store datasets and files.
+  rustfs:
+    image: rustfs/rustfs:1.0.0-rc.6
+    container_name: texera-rustfs
     ports:
-      - "${MINIO_PORT:-9000}:9000"
+      - "${RUSTFS_PORT:-9000}:9000"
     env_file:
       - .env
     environment:
-      - MINIO_ROOT_USER=${STORAGE_S3_AUTH_USERNAME}
-      - MINIO_ROOT_PASSWORD=${STORAGE_S3_AUTH_PASSWORD}
+      - RUSTFS_ACCESS_KEY=${STORAGE_S3_AUTH_USERNAME}
+      - RUSTFS_SECRET_KEY=${STORAGE_S3_AUTH_PASSWORD}
+      # Must match STORAGE_S3_REGION: the region is part of the SigV4 scope, 
and
+      # LakeFS sends its blockstore region on every request.
+      - RUSTFS_REGION=${STORAGE_S3_REGION}
+      - RUSTFS_CONSOLE_ENABLE=true
+      - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
+      # Empty log directory => log to stdout, so `docker compose logs rustfs`
+      # works and no second volume is needed. `warn` keeps that readable; the
+      # server is very chatty at info.
+      - RUSTFS_OBS_LOG_DIRECTORY=
+      - RUSTFS_OBS_LOGGER_LEVEL=warn
     volumes:
-      - minio_data:/data
-    command: server --console-address ":9001" /data
+      - rustfs_data:/data
     healthcheck:
-      test: ["CMD", "curl", "-sf", "http://localhost:9000/minio/health/live";]
+      test: ["CMD", "curl", "-sf", "http://localhost:9000/health";]
       interval: 5s
       timeout: 3s
       retries: 10
 
   # This job creates the S3 bucket used by the Iceberg warehouse that the
-  # Lakekeeper service manages.
-  minio-init:
-    image: minio/mc:RELEASE.2025-05-21T01-59-54Z
-    container_name: texera-minio-init
+  # Lakekeeper service manages. `rc` is RustFS's S3 client, the counterpart of
+  # the `mc` client this used to run.
+  rustfs-init:
+    image: rustfs/rc:v0.1.35
+    container_name: texera-rustfs-init
     depends_on:
-      minio:
+      rustfs:
         condition: service_healthy
     env_file:
       - .env
@@ -53,9 +63,10 @@ services:
     command:
       - |
         set -e
-        mc alias set local "$$STORAGE_S3_ENDPOINT" 
"$$STORAGE_S3_AUTH_USERNAME" "$$STORAGE_S3_AUTH_PASSWORD"
-        mc mb --ignore-existing 
"local/$$STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET"
-        echo "MinIO bucket '$$STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET' is 
ready."
+        rc alias set local "$$STORAGE_S3_ENDPOINT" 
"$$STORAGE_S3_AUTH_USERNAME" "$$STORAGE_S3_AUTH_PASSWORD" \
+          --region "$$STORAGE_S3_REGION" --bucket-lookup path
+        rc bucket create --ignore-existing 
"local/$$STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET"
+        echo "RustFS bucket '$$STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET' is 
ready."
 
   # PostgreSQL with PGroonga extension for full-text search.
   # Used by lakeFS and Texera's metadata storage.
@@ -83,13 +94,13 @@ services:
     depends_on:
       postgres:
         condition: service_healthy
-      minio:
+      rustfs:
         condition: service_started
     env_file:
       - .env
     environment:
-      # This port also need to be changed if the port of MinIO service is 
changed
-      - 
LAKEFS_BLOCKSTORE_S3_PRE_SIGNED_ENDPOINT=${TEXERA_HOST}:${MINIO_PORT:-9000}
+      # This port also need to be changed if the port of RustFS service is 
changed
+      - 
LAKEFS_BLOCKSTORE_S3_PRE_SIGNED_ENDPOINT=${TEXERA_HOST}:${RUSTFS_PORT:-9000}
       - 
LAKEFS_BLOCKSTORE_S3_CREDENTIALS_ACCESS_KEY_ID=${STORAGE_S3_AUTH_USERNAME}
       - 
LAKEFS_BLOCKSTORE_S3_CREDENTIALS_SECRET_ACCESS_KEY=${STORAGE_S3_AUTH_PASSWORD}
     entrypoint: ["/bin/sh", "-c"]
@@ -126,7 +137,7 @@ services:
     depends_on:
       postgres:
         condition: service_healthy
-      minio:
+      rustfs:
         condition: service_started
       lakekeeper-migrate:
         condition: service_completed_successfully
@@ -142,14 +153,14 @@ services:
       start_period: 10s
 
   # One-shot init container that creates the Lakekeeper default project and
-  # the Iceberg warehouse pointing at the MinIO bucket prepared by minio-init.
+  # the Iceberg warehouse pointing at the RustFS bucket prepared by 
rustfs-init.
   lakekeeper-init:
     image: alpine:3.19
     container_name: texera-lakekeeper-init
     depends_on:
       lakekeeper:
         condition: service_healthy
-      minio-init:
+      rustfs-init:
         condition: service_completed_successfully
     env_file:
       - .env
@@ -222,7 +233,7 @@ services:
           # The warehouse persists its S3 endpoint in Lakekeeper's own DB. The
           # local-dev launcher sets STORAGE_S3_ENDPOINT to the host LAN IP so
           # both this container and the host JVMs (Iceberg remote-signing) 
reach
-          # the same MinIO — but that IP changes across networks / DHCP leases.
+          # the same RustFS — but that IP changes across networks / DHCP 
leases.
           # This init is idempotent and would otherwise just skip, leaving a
           # stale endpoint that breaks workflow execution with an opaque 
Iceberg
           # RESTException. Refresh the stored endpoint on every run (#6195).
@@ -314,7 +325,7 @@ services:
     container_name: file-service
     restart: unless-stopped
     depends_on:
-      minio:
+      rustfs:
         condition: service_started
       lakefs:
         condition: service_healthy
@@ -536,6 +547,6 @@ networks:
 
 # persistent volumes
 volumes:
-  minio_data:
+  rustfs_data:
   postgres_data:
   workflow_result_data:
\ No newline at end of file
diff --git a/build.sbt b/build.sbt
index 67b838a2a0..11f2e9447e 100644
--- a/build.sbt
+++ b/build.sbt
@@ -167,6 +167,7 @@ lazy val FileService = (project in file("file-service"))
   .dependsOn(WorkflowCore, Auth, Config)
   .configs(Test)
   .dependsOn(DAO % "test->test") // test scope dependency
+  .dependsOn(WorkflowCore % "test->test") // reuse RustFSContainer in 
MockLakeFS
   .settings(
     dependencyOverrides ++= Seq(
       // override it as io.dropwizard 4 require 2.16.1 or higher
diff --git a/common/config/src/main/resources/storage.conf 
b/common/config/src/main/resources/storage.conf
index 12a9919e04..244d2a171e 100644
--- a/common/config/src/main/resources/storage.conf
+++ b/common/config/src/main/resources/storage.conf
@@ -125,7 +125,7 @@ storage {
         }
 
         auth {
-            username = "texera_minio"
+            username = "texera_rustfs"
             username = ${?STORAGE_S3_AUTH_USERNAME}
 
             password = "password"
diff --git a/common/workflow-core/build.sbt b/common/workflow-core/build.sbt
index bdaa923827..391113f089 100644
--- a/common/workflow-core/build.sbt
+++ b/common/workflow-core/build.sbt
@@ -87,8 +87,9 @@ libraryDependencies ++= Seq(
   "org.scalatest" %% "scalatest" % "3.2.15" % Test,                 // 
ScalaTest
   "junit" % "junit" % "4.13.2" % Test,                              // JUnit
   "com.novocode" % "junit-interface" % "0.11" % Test,               // SBT 
interface for JUnit
-  "com.dimafeng" %% "testcontainers-scala-scalatest" % testcontainersVersion % 
Test,   // Testcontainers ScalaTest integration
-  "com.dimafeng" %% "testcontainers-scala-minio" % testcontainersVersion % 
Test        // MinIO Testcontainer Scala integration
+  // RustFS has no dedicated testcontainers-scala module; RustFSContainer 
builds it on the
+  // GenericContainer that testcontainers-scala-core (pulled in by -scalatest) 
provides.
+  "com.dimafeng" %% "testcontainers-scala-scalatest" % testcontainersVersion % 
Test    // Testcontainers ScalaTest integration
 )
 
 
diff --git 
a/common/workflow-core/src/main/scala/org/apache/texera/service/util/S3StorageClient.scala
 
b/common/workflow-core/src/main/scala/org/apache/texera/service/util/S3StorageClient.scala
index 956de75520..11a9056055 100644
--- 
a/common/workflow-core/src/main/scala/org/apache/texera/service/util/S3StorageClient.scala
+++ 
b/common/workflow-core/src/main/scala/org/apache/texera/service/util/S3StorageClient.scala
@@ -30,7 +30,7 @@ import java.io.InputStream
 import scala.jdk.CollectionConverters._
 
 /**
-  * S3Storage provides an abstraction for S3-compatible storage (e.g., MinIO).
+  * S3Storage provides an abstraction for S3-compatible storage (e.g., RustFS).
   * - Uses credentials and endpoint from StorageConfig.
   * - Supports object upload, download, listing, and deletion.
   */
@@ -44,14 +44,14 @@ object S3StorageClient {
   // Cap how many failed keys are listed in the error message.
   private[util] val MAX_LISTED_DELETE_ERRORS = 10
 
-  // Initialize MinIO-compatible S3 Client
+  // Initialize the S3 client for the configured S3-compatible endpoint
   private lazy val s3Client: S3Client = {
     val credentials = AwsBasicCredentials.create(StorageConfig.s3Username, 
StorageConfig.s3Password)
     S3Client
       .builder()
       .credentialsProvider(StaticCredentialsProvider.create(credentials))
       .region(Region.of(StorageConfig.s3Region))
-      .endpointOverride(java.net.URI.create(StorageConfig.s3Endpoint)) // 
MinIO URL
+      .endpointOverride(java.net.URI.create(StorageConfig.s3Endpoint)) // 
object-store URL
       .serviceConfiguration(
         S3Configuration.builder().pathStyleAccessEnabled(true).build()
       )
@@ -106,7 +106,7 @@ object S3StorageClient {
     * A trailing `/` is added when missing so the prefix matches on a path 
boundary (`a/b` deletes
     * `a/b/file` but not `a/bc/file`). An empty prefix would match the whole 
bucket and is rejected.
     *
-    * @param bucketName Target S3/MinIO bucket.
+    * @param bucketName Target S3 bucket.
     * @param directoryPrefix Non-empty key prefix to delete.
     */
   def deleteDirectory(bucketName: String, directoryPrefix: String): Unit = {
diff --git 
a/common/workflow-core/src/test/scala/org/apache/texera/service/util/RustFSContainer.scala
 
b/common/workflow-core/src/test/scala/org/apache/texera/service/util/RustFSContainer.scala
new file mode 100644
index 0000000000..fd07258f73
--- /dev/null
+++ 
b/common/workflow-core/src/test/scala/org/apache/texera/service/util/RustFSContainer.scala
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.texera.service.util
+
+import com.dimafeng.testcontainers.GenericContainer
+import org.testcontainers.containers.wait.strategy.Wait
+
+import java.time.Duration
+
+/**
+  * Factory for the RustFS container used by the S3-backed test suites.
+  *
+  * testcontainers-scala ships a `MinIOContainer` module but no RustFS one, so 
this builds the
+  * equivalent on top of `GenericContainer`. The differences from 
`MinIOContainer` that matter:
+  *
+  *   - credentials come from `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` rather 
than
+  *     `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD`;
+  *   - readiness is `GET /health` on the S3 port, not `GET 
/minio/health/live`;
+  *   - `RUSTFS_REGION` must match the region the AWS SDK signs with, because 
the region is part
+  *     of the SigV4 credential scope.
+  *
+  * The console (9001) is left disabled: no test drives it, and starting it 
only widens the
+  * surface the readiness check has to wait for.
+  */
+object RustFSContainer {
+
+  /** Image pinned in lockstep with `bin/single-node/docker-compose.yml` and 
`bin/k8s/values.yaml`. */
+  val ImageName: String = "rustfs/rustfs:1.0.0-rc.6"
+
+  /** S3 API port inside the container. */
+  val Port: Int = 9000
+
+  val DefaultUser: String = "texera_rustfs"
+  val DefaultPassword: String = "password"
+  val DefaultRegion: String = "us-west-2"
+
+  def apply(
+      userName: String = DefaultUser,
+      password: String = DefaultPassword,
+      region: String = DefaultRegion
+  ): GenericContainer = {
+    val container = GenericContainer(
+      dockerImage = ImageName,
+      exposedPorts = Seq(Port),
+      env = Map(
+        "RUSTFS_ACCESS_KEY" -> userName,
+        "RUSTFS_SECRET_KEY" -> password,
+        "RUSTFS_REGION" -> region,
+        "RUSTFS_VOLUMES" -> "/data",
+        // Log to stdout so a failed start shows up in the testcontainers log 
consumer
+        // instead of a file inside the container. `warn` keeps that readable.
+        "RUSTFS_OBS_LOG_DIRECTORY" -> "",
+        "RUSTFS_OBS_LOGGER_LEVEL" -> "warn"
+      ),
+      waitStrategy = Wait
+        .forHttp("/health")
+        .forPort(Port)
+        .forStatusCode(200)
+        .withStartupTimeout(Duration.ofMinutes(2))
+    )
+    container
+  }
+}
diff --git 
a/common/workflow-core/src/test/scala/org/apache/texera/service/util/S3StorageTestBase.scala
 
b/common/workflow-core/src/test/scala/org/apache/texera/service/util/S3StorageTestBase.scala
index 1d3b77dbb1..4a25535493 100644
--- 
a/common/workflow-core/src/test/scala/org/apache/texera/service/util/S3StorageTestBase.scala
+++ 
b/common/workflow-core/src/test/scala/org/apache/texera/service/util/S3StorageTestBase.scala
@@ -19,14 +19,13 @@
 
 package org.apache.texera.service.util
 
-import com.dimafeng.testcontainers.MinIOContainer
+import com.dimafeng.testcontainers.GenericContainer
 import org.apache.texera.amber.config.StorageConfig
 import org.scalatest.{BeforeAndAfterAll, Suite}
-import org.testcontainers.utility.DockerImageName
 
 /**
-  * Base trait for tests requiring S3 storage (MinIO).
-  * Provides access to a single shared MinIO container across all test suites.
+  * Base trait for tests requiring S3 storage (RustFS).
+  * Provides access to a single shared RustFS container across all test suites.
   *
   * Usage: Mix this trait into any test suite that needs S3 storage.
   */
@@ -40,21 +39,17 @@ trait S3StorageTestBase extends BeforeAndAfterAll { this: 
Suite =>
 }
 
 object S3StorageTestBase {
-  private lazy val container: MinIOContainer = {
-    val c = MinIOContainer(
-      dockerImageName = 
DockerImageName.parse("minio/minio:RELEASE.2025-02-28T09-55-16Z"),
-      userName = "texera_minio",
-      password = "password"
-    )
+  private lazy val container: GenericContainer = {
+    val c = RustFSContainer(region = StorageConfig.s3Region)
     c.start()
 
-    val endpoint = s"http://${c.host}:${c.mappedPort(9000)}"
+    val endpoint = s"http://${c.host}:${c.mappedPort(RustFSContainer.Port)}"
     StorageConfig.s3Endpoint = endpoint
 
-    println(s"[S3Storage] Started shared MinIO at $endpoint")
+    println(s"[S3Storage] Started shared RustFS at $endpoint")
 
     sys.addShutdownHook {
-      println("[S3Storage] Stopping shared MinIO...")
+      println("[S3Storage] Stopping shared RustFS...")
       c.stop()
     }
 
diff --git a/docs/contribution-guidelines/guide-for-developers.md 
b/docs/contribution-guidelines/guide-for-developers.md
index 6871b2c636..c6e7a4f5b7 100644
--- a/docs/contribution-guidelines/guide-for-developers.md
+++ b/docs/contribution-guidelines/guide-for-developers.md
@@ -147,9 +147,9 @@ Execute `sql/iceberg_postgres_catalog.sql`  to create the 
database for storing I
 psql -U postgres -f "sql/iceberg_postgres_catalog.sql"
 ```
 
-### Setup the LakeFS+Minio locally
+### Setup the LakeFS+RustFS locally
 
-Texera requires [LakeFS](https://lakefs.io/) and 
S3([Minio](https://min.io/docs/minio/kubernetes/upstream/index.html) is one of 
the implementations) as the dataset storage. Setting up these two storage 
services locally are required to make Texera's dataset feature functioning.
+Texera requires [LakeFS](https://lakefs.io/) and 
S3([RustFS](https://docs.rustfs.com/) is one of the implementations) as the 
dataset storage. Setting up these two storage services locally are required to 
make Texera's dataset feature functioning.
 
 Install [Docker 
Desktop](https://docs.docker.com/desktop/setup/install/mac-install/) which 
contains both docker engine and docker compose. Make sure you launch the Docker 
after installing it.
 
@@ -160,7 +160,7 @@ cd file-service/src/main/resources
 
 Edit `docker-compose.yml` by: search for `volumes` in the file and follow the 
instructions in the comment. This step is required otherwise your data will be 
lost if containers are deleted
 
-Execute the following command to start LakeFS and Minio:
+Execute the following command to start LakeFS and RustFS:
 ```
 docker compose up
 ```
diff --git a/docs/getting-started/installing-using-docker.md 
b/docs/getting-started/installing-using-docker.md
index 52968886d9..ce8b28df23 100644
--- a/docs/getting-started/installing-using-docker.md
+++ b/docs/getting-started/installing-using-docker.md
@@ -146,11 +146,11 @@ All changes below are to the `.env` file in the 
installation folder, unless othe
 ### Run Texera on other ports
 By default, Texera uses:
 - Port 8080 for its web service
-- Port 9000 for its MinIO storage service
+- Port 9000 for its RustFS storage service
 
 To change these ports, open the `.env` file and update the corresponding 
variables:
 - For the web service port (8080): change `TEXERA_PORT=8080` to your desired 
port, e.g., `TEXERA_PORT=8081`.
-- For the MinIO port (9000): change `MINIO_PORT=9000` to your desired port, 
e.g., `MINIO_PORT=9001`.
+- For the RustFS port (9000): change `RUSTFS_PORT=9000` to your desired port, 
e.g., `RUSTFS_PORT=9001`.
 
 ### Change the locations of Texera data
 By default, Docker manages Texera's data locations. To change them to your own 
locations:
diff --git a/docs/getting-started/run-on-kubernetes.md 
b/docs/getting-started/run-on-kubernetes.md
index 6503b8dc91..d51c3e3ce0 100644
--- a/docs/getting-started/run-on-kubernetes.md
+++ b/docs/getting-started/run-on-kubernetes.md
@@ -110,12 +110,12 @@ Once the deployments are running, you can access the 
Texera web interface.
 
 ### File Upload Error
 
-If you see an error when trying to upload a file to a dataset, you may need to 
forward the port for MinIO (our file storage service).
+If you see an error when trying to upload a file to a dataset, you may need to 
forward the port for RustFS (our file storage service).
 
 Run the following command in a new terminal:
 
 ```bash
-kubectl port-forward -n texera-dev service/texera-minio 31000:9000
+kubectl port-forward -n texera-dev service/texera-rustfs-svc 31000:9000
 ```
 
 This maps the service's port `9000` to your local port `31000`.
diff --git a/docs/overview.md b/docs/overview.md
index 2b232db723..08e1adde76 100644
--- a/docs/overview.md
+++ b/docs/overview.md
@@ -37,7 +37,7 @@ At its core, Texera acts as a bridge between a highly 
accessible frontend and a
 
 1. **Web-Based Interface (Frontend):** A rich GUI running directly in your 
browser. It allows users to construct data processing pipelines by dragging and 
dropping blocks on a canvas. No installation is required on client machines.
 2. **Distributed Engine (Backend):** When a workflow is submitted, the Texera 
engine compiles the graphical representation into an optimized, distributed 
execution plan. It then spins up computing units to process massive datasets in 
parallel.
-3. **Storage Integration:** Texera integrates smoothly with modern data lake 
and storage technologies (like LakeFS and MinIO) to persistently log runs and 
save datasets securely.
+3. **Storage Integration:** Texera integrates smoothly with modern data lake 
and storage technologies (like LakeFS and RustFS) to persistently log runs and 
save datasets securely.
 
 ---
 
diff --git a/docs/tutorials/guide-to-launch-lakekeeper.md 
b/docs/tutorials/guide-to-launch-lakekeeper.md
index a2eb5d9a81..fc4eedfb14 100644
--- a/docs/tutorials/guide-to-launch-lakekeeper.md
+++ b/docs/tutorials/guide-to-launch-lakekeeper.md
@@ -71,7 +71,7 @@ For more information of why using RESTCatalog, see [Issue 
#4126](https://github.
   
+LAKEKEEPER__PG_DATABASE_URL_WRITE="postgres://<user>:<urlencoded_password>@<host>:5432/texera_lakekeeper"
   ```           
 
-  If you have customized storage-related values in 
`common/config/src/main/resources/storage.conf` (for example, the bucket name, 
S3 endpoint, or MinIO credentials), check the below environment variables in 
the script and modify their values accordingly:
+  If you have customized storage-related values in 
`common/config/src/main/resources/storage.conf` (for example, the bucket name, 
S3 endpoint, or RustFS credentials), check the below environment variables in 
the script and modify their values accordingly:
 
 ```shell
   # Storage settings — must stay in sync with storage.conf
@@ -81,7 +81,7 @@ 
STORAGE_ICEBERG_CATALOG_REST_WAREHOUSE_NAME="${STORAGE_ICEBERG_CATALOG_REST_WARE
 
STORAGE_ICEBERG_CATALOG_REST_REGION="${STORAGE_ICEBERG_CATALOG_REST_REGION:-us-west-2}"
 
STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET="${STORAGE_ICEBERG_CATALOG_REST_S3_BUCKET:-texera-iceberg}"
 STORAGE_S3_ENDPOINT="${STORAGE_S3_ENDPOINT:-http://localhost:9000}";
-STORAGE_S3_AUTH_USERNAME="${STORAGE_S3_AUTH_USERNAME:-texera_minio}"
+STORAGE_S3_AUTH_USERNAME="${STORAGE_S3_AUTH_USERNAME:-texera_rustfs}"
 STORAGE_S3_AUTH_PASSWORD="${STORAGE_S3_AUTH_PASSWORD:-password}"
 ```                                                                            
                                                                                
                                                                                
                                                                                
                                                          
                   
@@ -96,7 +96,7 @@ 
STORAGE_S3_AUTH_PASSWORD="${STORAGE_S3_AUTH_PASSWORD:-password}"
 
   1. Start Lakekeeper if it's not already running (on http://localhost:8181)   
                                                              
   2. Bootstrap the Lakekeeper server (creates the default project)
-  3. Create the texera-iceberg bucket in MinIO if it doesn't exist             
                                                              
+  3. Create the texera-iceberg bucket in RustFS if it doesn't exist            
                                                               
   4. Register the texera warehouse with Lakekeeper, pointing at that bucket    
                                                                                
                        
                                                                                
                       
                   
diff --git a/file-service/build.sbt b/file-service/build.sbt
index c93f5e3230..f816443d21 100644
--- a/file-service/build.sbt
+++ b/file-service/build.sbt
@@ -74,9 +74,10 @@ libraryDependencies ++= Seq(
   "org.mockito" % "mockito-core" % mockitoVersion % Test,            // 
Mockito for mocking
   "org.assertj" % "assertj-core" % assertjVersion % Test,            // 
AssertJ for assertions
   "com.novocode" % "junit-interface" % "0.11" % Test,                // SBT 
interface for JUnit
+  // RustFS has no dedicated testcontainers-scala module; MockLakeFS uses the 
shared
+  // RustFSContainer from WorkflowCore's test sources (see the test->test dep 
in build.sbt).
   "com.dimafeng" %% "testcontainers-scala-scalatest" % testcontainersVersion % 
Test,   // Testcontainers ScalaTest integration
   "com.dimafeng" %% "testcontainers-scala-postgresql" % testcontainersVersion 
% Test,  // PostgreSQL Testcontainer Scala integration
-  "com.dimafeng" %% "testcontainers-scala-minio" % testcontainersVersion % 
Test,       // MinIO Testcontainer Scala integration
 )
 
 /////////////////////////////////////////////////////////////////////////////
diff --git a/file-service/src/main/resources/docker-compose.yml 
b/file-service/src/main/resources/docker-compose.yml
index f4d2a07256..95088150b1 100644
--- a/file-service/src/main/resources/docker-compose.yml
+++ b/file-service/src/main/resources/docker-compose.yml
@@ -17,20 +17,23 @@
 
 name: texera-lakefs
 services:
-  minio:
-    image: minio/minio:RELEASE.2025-02-28T09-55-16Z
-    container_name: texera-lakefs-minio
+  rustfs:
+    image: rustfs/rustfs:1.0.0-rc.6
+    container_name: texera-lakefs-rustfs
     restart: unless-stopped
     ports:
       - "9000:9000"
       - "9001:9001"
     environment:
-      - MINIO_ROOT_USER=texera_minio
-      - MINIO_ROOT_PASSWORD=password
-    command: server --console-address ":9001" /data
+      - RUSTFS_ACCESS_KEY=texera_rustfs
+      - RUSTFS_SECRET_KEY=password
+      # Must match the region the AWS SDK signs with: it is part of the SigV4 
scope.
+      - RUSTFS_REGION=us-west-2
+      - RUSTFS_CONSOLE_ENABLE=true
+      - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
     # The lines below are recommended to mount a host path in order to persist 
your data even if the container is removed.
     volumes:
-      - minio_data:/data
+      - rustfs_data:/data
 
   postgres:
     image: postgres:15
@@ -56,16 +59,16 @@ services:
     depends_on:
       postgres:
         condition: service_healthy
-      minio:
+      rustfs:
         condition: service_started
     ports:
       - "8000:8000"
     environment:
       - LAKEFS_BLOCKSTORE_TYPE=s3
       - LAKEFS_BLOCKSTORE_S3_FORCE_PATH_STYLE=true
-      - LAKEFS_BLOCKSTORE_S3_ENDPOINT=http://minio:9000
+      - LAKEFS_BLOCKSTORE_S3_ENDPOINT=http://rustfs:9000
       - LAKEFS_BLOCKSTORE_S3_PRE_SIGNED_ENDPOINT=http://localhost:9000
-      - LAKEFS_BLOCKSTORE_S3_CREDENTIALS_ACCESS_KEY_ID=texera_minio
+      - LAKEFS_BLOCKSTORE_S3_CREDENTIALS_ACCESS_KEY_ID=texera_rustfs
       - LAKEFS_BLOCKSTORE_S3_CREDENTIALS_SECRET_ACCESS_KEY=password
       - LAKEFS_AUTH_ENCRYPT_SECRET_KEY=random_string_for_lakefs
       - LAKEFS_LOGGING_LEVEL=INFO
@@ -94,5 +97,5 @@ networks:
 
 # Named Docker volumes — uncomment the following lines if you mounted host 
paths above.
 volumes:
-  minio_data:
+  rustfs_data:
   postgres_data:
diff --git a/file-service/src/main/resources/minio-config.yml 
b/file-service/src/main/resources/rustfs-config.yml
similarity index 69%
rename from file-service/src/main/resources/minio-config.yml
rename to file-service/src/main/resources/rustfs-config.yml
index 0622b92bdc..f29fed9e27 100644
--- a/file-service/src/main/resources/minio-config.yml
+++ b/file-service/src/main/resources/rustfs-config.yml
@@ -18,15 +18,20 @@
 version: '3.8'
 
 services:
-  minio:
-    image: minio/minio:latest
-    container_name: minio
+  rustfs:
+    image: rustfs/rustfs:1.0.0-rc.6
+    container_name: rustfs
     ports:
-      - "9500:9000"   # MinIO API
-      - "9501:9001"   # MinIO Console UI
+      - "9500:9000"   # S3 API
+      - "9501:9001"   # Console UI
     environment:
-      - MINIO_ROOT_USER=texera_minio
-      - MINIO_ROOT_PASSWORD=password
+      - RUSTFS_ACCESS_KEY=texera_rustfs
+      - RUSTFS_SECRET_KEY=password
+      - RUSTFS_REGION=us-west-2
+      - RUSTFS_CONSOLE_ENABLE=true
+      - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
     volumes:
-      - 
/Users/baijiadong/Desktop/chenlab/texera/core/file-service/src/main/user-resources/minio:/data
-    command: server --console-address ":9001" /data
\ No newline at end of file
+      - rustfs_data:/data
+
+volumes:
+  rustfs_data:
\ No newline at end of file
diff --git 
a/file-service/src/test/scala/org/apache/texera/service/MockLakeFS.scala 
b/file-service/src/test/scala/org/apache/texera/service/MockLakeFS.scala
index 62e60bd267..35ddbb4405 100644
--- a/file-service/src/test/scala/org/apache/texera/service/MockLakeFS.scala
+++ b/file-service/src/test/scala/org/apache/texera/service/MockLakeFS.scala
@@ -22,7 +22,7 @@ package org.apache.texera.service
 import com.dimafeng.testcontainers._
 import io.lakefs.clients.sdk.{ApiClient, RepositoriesApi}
 import org.apache.texera.amber.config.StorageConfig
-import org.apache.texera.service.util.S3StorageClient
+import org.apache.texera.service.util.{RustFSContainer, S3StorageClient}
 import org.scalatest.{BeforeAndAfterAll, Suite}
 import org.testcontainers.containers.Network
 import org.testcontainers.utility.DockerImageName
@@ -34,7 +34,7 @@ import software.amazon.awssdk.services.s3.S3Configuration
 import java.net.URI
 
 /**
-  * Trait to spin up a LakeFS + MinIO + Postgres stack using Testcontainers,
+  * Trait to spin up a LakeFS + RustFS + Postgres stack using Testcontainers,
   * similar to how MockTexeraDB uses EmbeddedPostgres.
   */
 trait MockLakeFS extends ForAllTestContainer with BeforeAndAfterAll { self: 
Suite =>
@@ -53,29 +53,27 @@ trait MockLakeFS extends ForAllTestContainer with 
BeforeAndAfterAll { self: Suit
   postgres.container.withNetwork(network)
 
   // LakeFS bakes the pre-signed endpoint into its env before containers start,
-  // so MinIO cannot use a dynamically mapped host port: presigned URLs must be
-  // reachable from the host at an address known ahead of time. Reserve a free
-  // host port and pin MinIO's 9000 to it.
-  val minioHostPort: Int = {
+  // so the object store cannot use a dynamically mapped host port: presigned 
URLs
+  // must be reachable from the host at an address known ahead of time. 
Reserve a
+  // free host port and pin the S3 port to it.
+  val objectStoreHostPort: Int = {
     val socket = new java.net.ServerSocket(0)
     try socket.getLocalPort
     finally socket.close()
   }
 
-  // MinIO for object storage
-  val minio = MinIOContainer(
-    dockerImageName = 
DockerImageName.parse("minio/minio:RELEASE.2025-02-28T09-55-16Z"),
-    userName = "texera_minio",
-    password = "password"
-  )
-  minio.container.withNetwork(network)
-  minio.container.withCreateContainerCmdModifier { cmd =>
+  // RustFS for object storage
+  val objectStore: GenericContainer = RustFSContainer()
+  objectStore.container.withNetwork(network)
+  objectStore.container.withCreateContainerCmdModifier { cmd =>
     import com.github.dockerjava.api.model.{ExposedPort, PortBinding, Ports}
-    // setting explicit bindings replaces them all, so 9001 (console) must keep
-    // a dynamic binding or the container readiness check never passes
+    // RustFSContainer exposes only the S3 port, so pinning it is the whole
+    // binding set; the console is not started.
     cmd.getHostConfig.withPortBindings(
-      new PortBinding(Ports.Binding.bindPort(minioHostPort), 
ExposedPort.tcp(9000)),
-      new PortBinding(Ports.Binding.empty(), ExposedPort.tcp(9001))
+      new PortBinding(
+        Ports.Binding.bindPort(objectStoreHostPort),
+        ExposedPort.tcp(RustFSContainer.Port)
+      )
     )
   }
 
@@ -98,10 +96,11 @@ trait MockLakeFS extends ForAllTestContainer with 
BeforeAndAfterAll { self: Suit
     env = Map(
       "LAKEFS_BLOCKSTORE_TYPE" -> "s3",
       "LAKEFS_BLOCKSTORE_S3_FORCE_PATH_STYLE" -> "true",
-      "LAKEFS_BLOCKSTORE_S3_ENDPOINT" -> 
s"http://${minio.container.getNetworkAliases.get(0)}:9000",
-      "LAKEFS_BLOCKSTORE_S3_PRE_SIGNED_ENDPOINT" -> 
s"http://localhost:$minioHostPort";,
-      "LAKEFS_BLOCKSTORE_S3_CREDENTIALS_ACCESS_KEY_ID" -> "texera_minio",
-      "LAKEFS_BLOCKSTORE_S3_CREDENTIALS_SECRET_ACCESS_KEY" -> "password",
+      "LAKEFS_BLOCKSTORE_S3_ENDPOINT" -> 
s"http://${objectStore.container.getNetworkAliases
+        .get(0)}:${RustFSContainer.Port}",
+      "LAKEFS_BLOCKSTORE_S3_PRE_SIGNED_ENDPOINT" -> 
s"http://localhost:$objectStoreHostPort";,
+      "LAKEFS_BLOCKSTORE_S3_CREDENTIALS_ACCESS_KEY_ID" -> 
RustFSContainer.DefaultUser,
+      "LAKEFS_BLOCKSTORE_S3_CREDENTIALS_SECRET_ACCESS_KEY" -> 
RustFSContainer.DefaultPassword,
       "LAKEFS_AUTH_ENCRYPT_SECRET_KEY" -> "random_string_for_lakefs",
       "LAKEFS_LOGGING_LEVEL" -> "INFO",
       "LAKEFS_STATS_ENABLED" -> "1",
@@ -114,10 +113,11 @@ trait MockLakeFS extends ForAllTestContainer with 
BeforeAndAfterAll { self: Suit
   )
   lakefs.container.withNetwork(network)
 
-  override val container = MultipleContainers(postgres, minio, lakefs)
+  override val container = MultipleContainers(postgres, objectStore, lakefs)
 
   def lakefsBaseUrl: String = 
s"http://${lakefs.host}:${lakefs.mappedPort(8000)}"
-  def minioEndpoint: String = s"http://${minio.host}:${minio.mappedPort(9000)}"
+  def objectStoreEndpoint: String =
+    
s"http://${objectStore.host}:${objectStore.mappedPort(RustFSContainer.Port)}"
   def lakefsApiBasePath: String = s"$lakefsBaseUrl/api/v1"
 
   // ---- Clients (lazy so they initialize after containers are started) ----
@@ -134,20 +134,20 @@ trait MockLakeFS extends ForAllTestContainer with 
BeforeAndAfterAll { self: Suit
   lazy val repositoriesApi: RepositoriesApi = new 
RepositoriesApi(lakefsApiClient)
 
   /**
-    * S3 client instance for testing pointed at MinIO.
+    * S3 client instance for testing pointed at RustFS.
     *
     * Notes:
-    * - Region can be any value for MinIO, but MUST match what your signing 
expects.
-    *   so we use that.
+    * - The region MUST match RUSTFS_REGION: it is part of the SigV4 
credential scope.
     * - Path-style is important: http://host:port/bucket/key
     */
   lazy val s3Client: S3Client = {
     //Temporal credentials for testing purposes only
-    val creds = AwsBasicCredentials.create("texera_minio", "password")
+    val creds =
+      AwsBasicCredentials.create(RustFSContainer.DefaultUser, 
RustFSContainer.DefaultPassword)
     S3Client
       .builder()
       .endpointOverride(URI.create(StorageConfig.s3Endpoint)) // set in 
afterStart()
-      .region(Region.US_WEST_2) // Required for `.build()`; not important in 
this test config.
+      .region(Region.US_WEST_2) // must match RustFSContainer's RUSTFS_REGION
       .credentialsProvider(StaticCredentialsProvider.create(creds))
       
.serviceConfiguration(S3Configuration.builder().pathStyleAccessEnabled(true).build())
       .build()
@@ -172,7 +172,7 @@ trait MockLakeFS extends ForAllTestContainer with 
BeforeAndAfterAll { self: Suit
     }
 
     // replace storage endpoints in StorageConfig
-    StorageConfig.s3Endpoint = minioEndpoint
+    StorageConfig.s3Endpoint = objectStoreEndpoint
     StorageConfig.lakefsEndpoint = lakefsApiBasePath
 
     // create S3 bucket used by lakeFS in tests

Reply via email to