Migrating to SeaweedFS

Move on-premises object storage from MinIO to SeaweedFS, including data transfer with rclone and the fossa-core cutover.

9 min readUpdated Jul 9, 2026

Overview

This guide moves your on-premises object storage backend from MinIO to SeaweedFS, FOSSA's S3-compatible storage option. FOSSA uses two SeaweedFS instances (one for Core analysis artifacts and one for Hubble compliance data) and the migration copies your existing MinIO data into them before cutting the deployment over.

The migration has two phases: prepare the SeaweedFS deployments, then execute the cutover during a maintenance window.

Warning

The cutover requires maintenance mode and a data transfer. Plan for downtime proportional to how much object data you're migrating.

Preparation

Create SeaweedFS deployments for Core and Hubble sized to your storage needs. FOSSA requires two instances:

  • core-seaweedfs: stores analysis artifacts for FOSSA Core
  • hubble-seaweedfs: stores compliance data for FOSSA Hubble

The template below documents installation, upgrade, and the sizing guide:

YAML
# =============================================================================# SeaweedFS Storage Values Template for FOSSA Deployment# =============================================================================## FOSSA requires two SeaweedFS instances for S3-compatible object storage:#   1. core-seaweedfs   - Stores analysis artifacts for FOSSA Core#   2. hubble-seaweedfs - Stores compliance data for FOSSA Hubble## Adjust storage sizes based on your expected workload. See the "Sizing Guide"# at the bottom of this file for recommendations.## Prerequisites:#   - A Kubernetes cluster with a default StorageClass configured#   - Helm 3.x## Installation:#   helm repo add fossa https://charts.fossa.com#   helm repo update##   helm install core-seaweedfs fossa/seaweedfs \#     --namespace <namespace> \#     --version 4.17.0 \#     -f core-seaweedfs-values.yaml##   helm install hubble-seaweedfs fossa/seaweedfs \#     --namespace <namespace> \#     --version 4.17.0 \#     -f hubble-seaweedfs-values.yaml## Upgrade:#   helm upgrade core-seaweedfs fossa/seaweedfs \#     --namespace <namespace> \#     --version 4.17.0 \#     -f core-seaweedfs-values.yaml##   helm upgrade hubble-seaweedfs fossa/seaweedfs \#     --namespace <namespace> \#     --version 4.17.0 \#     -f hubble-seaweedfs-values.yaml## =============================================================================# Sizing Guide# =============================================================================## The main values to adjust are volume.dataDirs[].size fields. These control# how much raw data each SeaweedFS instance can store.## Recommended volume sizes by deployment scale:##   Deployment Size         | Core volume.size | Hubble volume.size | Notes#   ------------------------|------------------|--------------------|-------------------#   Small  (<100 projects)  |  50Gi            |  25Gi              | Dev/eval environments#   Medium (<500 projects)  | 100Gi            |  50Gi              | Standard production#   Large  (<2000 projects) | 250Gi            | 100Gi              | Large organizations#   XLarge (2000+ projects) | 500Gi+           | 250Gi+             | Enterprise scale## Other parameters:##   master.data.size   - Volume metadata. 1Gi is sufficient for all but the#                        largest deployments (10k+ volumes).##   filer.data.size    - File index. 1Gi handles millions of objects. Increase#                        to 5-10Gi only if you expect tens of millions of files.##   maxVolumes         - Upper limit on the number of logical volumes SeaweedFS#                        can create. The defaults (10000 for core, 6000 for#                        hubble) are generous and rarely need adjustment.##   storageClass       - Uncomment and set if you need a specific Kubernetes#                        StorageClass (e.g., for SSD-backed storage, specific#                        provisioners, or cloud-specific volume types). If left#                        commented out, the cluster default StorageClass is used.## Storage can be expanded after initial deployment by increasing the PVC size,# provided your StorageClass supports volume expansion# (allowVolumeExpansion: true).# =============================================================================

Core: core-seaweedfs-values.yaml

After installation, the Core S3 endpoint is available at http://core-seaweedfs-s3:8333.

YAML
nameOverride: core-seaweedfs global:  serviceAccountName: core-seaweedfs  imagePullSecrets: test-fossa-core-quay.io # <-- existing pull secret created by fossa-core when release name is `test`; list them with `kubectl get secrets --field-selector type=kubernetes.io/dockerconfigjson` # -- S3 API Configurations3:  enabled: true  enableAuth: true  createBuckets:    - name: fossa.test      anonymousRead: false # -- Master Server Storage (volume metadata and cluster topology; lightweight)master:  data:    type: persistentVolumeClaim    size: 1Gi                            # <-- Adjust if needed (rarely necessary)    # storageClass: ""                   # <-- Uncomment to use a specific StorageClass # -- Filer Storage (file-to-chunk mapping index)filer:  data:    type: persistentVolumeClaim    size: 1Gi                            # <-- Adjust if needed    # storageClass: ""                   # <-- Uncomment to use a specific StorageClass  logs:    type: emptyDir # -- Volume Server Storage (primary data storage)volume:  dataDirs:    - name: data1      type: persistentVolumeClaim      size: 100Gi                        # <-- ADJUST: Set based on expected data volume      maxVolumes: 10000      # storageClass: ""                 # <-- Uncomment to use a specific StorageClass

Hubble: hubble-seaweedfs-values.yaml

After installation, the Hubble S3 endpoint is available at http://hubble-seaweedfs-s3:8333.

YAML
nameOverride: hubble-seaweedfs global:  serviceAccountName: hubble-seaweedfs  imagePullSecrets: test-hubble-quay.io # <-- existing pull secret created by fossa-core when release name is `test`; list them with `kubectl get secrets --field-selector type=kubernetes.io/dockerconfigjson` # -- S3 API Configurations3:  enabled: true  enableAuth: true  createBuckets:    - name: hubble.fossa.test      anonymousRead: false # -- Master Server Storage (volume metadata and cluster topology; lightweight)master:  data:    type: persistentVolumeClaim    size: 1Gi                            # <-- Adjust if needed (rarely necessary)    # storageClass: ""                   # <-- Uncomment to use a specific StorageClass # -- Filer Storage (file-to-chunk mapping index)filer:  data:    type: persistentVolumeClaim    size: 1Gi                            # <-- Adjust if needed    # storageClass: ""                   # <-- Uncomment to use a specific StorageClass  logs:    type: emptyDir # -- Volume Server Storage (primary data storage)volume:  dataDirs:    - name: data1      type: persistentVolumeClaim      size: 100Gi                         # <-- ADJUST: Set based on expected data volume      maxVolumes: 6000      # storageClass: ""                 # <-- Uncomment to use a specific StorageClass

Execution

  1. 1

    Schedule a maintenance window

    Pick a window long enough to transfer your object data and verify the cutover.

  2. 2

    Set the application to maintenance mode

    Assuming the namespace is fossa:

    Shell
    kubectl config set-context --current --namespace=fossahelm upgrade -i fossa fossa/fossa-core --values fossa-core-config.yml --set global.maintenanceMode.enabled=true --version "^4.0.0"
  3. 3

    Deploy SeaweedFS for Core and Hubble

    Shell
    helm upgrade -i core-seaweedfs fossa/seaweedfs --values core-seaweedfs-values.yamlhelm upgrade -i hubble-seaweedfs fossa/seaweedfs --values hubble-seaweedfs-values.yaml
  4. 4

    Transfer data from MinIO to SeaweedFS

    The Job below uses rclone to copy all data from Core and Hubble MinIO into SeaweedFS. Update the rclone.conf endpoints and credentials to match your deployment. The example assumes:

    • Core: bucket fossa.test; MinIO minio / minio123 at http://fossa-core-minio:80; SeaweedFS at http://core-seaweedfs-s3:8333
    • Hubble: bucket hubble.fossa.test; MinIO minio / minio123 at http://fossa-hubble-minio:80; SeaweedFS seaweedfs / seaweedfs123 at http://hubble-seaweedfs-s3:8333
    YAML
    # rclone-migration-minio-seaweedfs.yamlapiVersion: v1kind: ConfigMapmetadata:  name: rclone-migration-configdata:  rclone.conf: |    [minio-core]    type = s3    provider = Minio    access_key_id = minio    secret_access_key = minio123    endpoint = http://fossa-core-minio:80    region = minio     [minio-hubble]    type = s3    provider = Minio    access_key_id = minio    secret_access_key = minio123    endpoint = http://fossa-hubble-minio:80    region = minio     [seaweedfs-core]    type = s3    provider = Other    access_key_id = seaweedfs    secret_access_key = seaweedfs123    endpoint = http://core-seaweedfs-s3:8333    region = us-east-1    force_path_style = true     [seaweedfs-hubble]    type = s3    provider = Other    access_key_id = seaweedfs    secret_access_key = seaweedfs123    endpoint = http://hubble-seaweedfs-s3:8333    region = us-east-1    force_path_style = true   core-bucket: "fossa.test"  hubble-bucket: "hubble.fossa.test"  migrate.sh: |    #!/bin/sh    set -e     echo "============================================"    echo "  MinIO -> SeaweedFS Migration"    echo "  Started: $(date -u +%Y-%m-%dT%H:%M:%SZ)"    echo "============================================"     # ---------- tuning knobs (adjust for production) ----------    # TRANSFERS:  number of file transfers in parallel    # CHECKERS:   number of parallel hash-checking threads    # S3_CONCURRENCY: multipart upload concurrency per file    # S3_CHUNK:   multipart chunk size (bigger = fewer API calls for large files)    # BUFFER:     in-memory buffer per transfer    TRANSFERS="${RCLONE_TRANSFERS:-16}"    CHECKERS="${RCLONE_CHECKERS:-8}"    S3_CONCURRENCY="${RCLONE_S3_UPLOAD_CONCURRENCY:-4}"    S3_CHUNK="${RCLONE_S3_CHUNK_SIZE:-64M}"    BUFFER="${RCLONE_BUFFER_SIZE:-32M}"     COMMON_FLAGS="--transfers=$TRANSFERS \      --checkers=$CHECKERS \      --s3-upload-concurrency=$S3_CONCURRENCY \      --s3-chunk-size=$S3_CHUNK \      --buffer-size=$BUFFER \      --stats=10s \      --stats-one-line \      --log-level=INFO \      --checksum \      --retries=3 \      --retries-sleep=5s \      --low-level-retries=10"     # ---------- pre-flight checks ----------    echo ""    echo ">>> Pre-flight: listing source buckets"    echo "--- minio-core ---"    rclone lsd minio-core: 2>&1    echo "--- minio-hubble ---"    rclone lsd minio-hubble: 2>&1    echo "--- seaweedfs-core ---"    rclone lsd seaweedfs-core: 2>&1    echo "--- seaweedfs-hubble ---"    rclone lsd seaweedfs-hubble: 2>&1     echo ""    echo ">>> Source object counts"    CORE_SRC=$(rclone size minio-core:$CORE_BUCKET --json 2>/dev/null)    HUBBLE_SRC=$(rclone size minio-hubble:$HUBBLE_BUCKET --json 2>/dev/null)    echo "  core:   $CORE_SRC"    echo "  hubble: $HUBBLE_SRC"     # ---------- migration: core ----------    echo ""    echo "============================================"    echo "  [1/2] Migrating core: minio -> seaweedfs"    echo "============================================"    eval rclone sync minio-core:$CORE_BUCKET seaweedfs-core:$CORE_BUCKET $COMMON_FLAGS 2>&1    CORE_RC=$?    echo "  core migration exit code: $CORE_RC"     # ---------- migration: hubble ----------    echo ""    echo "============================================"    echo "  [2/2] Migrating hubble: minio -> seaweedfs"    echo "============================================"    eval rclone sync minio-hubble:$HUBBLE_BUCKET seaweedfs-hubble:$HUBBLE_BUCKET $COMMON_FLAGS 2>&1    HUBBLE_RC=$?    echo "  hubble migration exit code: $HUBBLE_RC"     # ---------- post-migration validation ----------    echo ""    echo "============================================"    echo "  Post-migration validation"    echo "============================================"     echo ""    echo ">>> Destination object counts"    CORE_DST=$(rclone size seaweedfs-core:$CORE_BUCKET --json 2>/dev/null)    HUBBLE_DST=$(rclone size seaweedfs-hubble:$HUBBLE_BUCKET --json 2>/dev/null)    echo "  core src:    $CORE_SRC"    echo "  core dst:    $CORE_DST"    echo "  hubble src:  $HUBBLE_SRC"    echo "  hubble dst:  $HUBBLE_DST"     echo ""    echo ">>> Checking for differences (core)..."    rclone check minio-core:$CORE_BUCKET seaweedfs-core:$CORE_BUCKET --one-way $COMMON_FLAGS 2>&1    CORE_CHECK=$?     echo ""    echo ">>> Checking for differences (hubble)..."    rclone check minio-hubble:$HUBBLE_BUCKET seaweedfs-hubble:$HUBBLE_BUCKET --one-way $COMMON_FLAGS 2>&1    HUBBLE_CHECK=$?     echo ""    echo "============================================"    echo "  Migration Summary"    echo "  Finished: $(date -u +%Y-%m-%dT%H:%M:%SZ)"    echo "============================================"    echo "  Core   sync=$CORE_RC   check=$CORE_CHECK"    echo "  Hubble sync=$HUBBLE_RC check=$HUBBLE_CHECK"     if [ "$CORE_RC" -ne 0 ] || [ "$HUBBLE_RC" -ne 0 ] || [ "$CORE_CHECK" -ne 0 ] || [ "$HUBBLE_CHECK" -ne 0 ]; then      echo "  STATUS: FAILED (see logs above)"      exit 1    fi     echo "  STATUS: SUCCESS"---apiVersion: batch/v1kind: Jobmetadata:  name: rclone-minio-to-seaweedfsspec:  backoffLimit: 2  ttlSecondsAfterFinished: 86400  template:    metadata:      labels:        app: rclone-migration    spec:      restartPolicy: OnFailure      containers:      - name: rclone        image: rclone/rclone:latest        imagePullPolicy: IfNotPresent        command: ["/bin/sh", "/scripts/migrate.sh"]        env:        - name: CORE_BUCKET          valueFrom:            configMapKeyRef:              name: rclone-migration-config              key: core-bucket        - name: HUBBLE_BUCKET          valueFrom:            configMapKeyRef:              name: rclone-migration-config              key: hubble-bucket        # --- Tune these for production (1-4TB) ---        # For a Kind test, keep conservative to avoid overwhelming the node        - name: RCLONE_TRANSFERS          value: "8"        - name: RCLONE_CHECKERS          value: "4"        - name: RCLONE_S3_UPLOAD_CONCURRENCY          value: "4"        - name: RCLONE_S3_CHUNK_SIZE          value: "16M"        - name: RCLONE_BUFFER_SIZE          value: "16M"        resources:          requests:            cpu: 500m            memory: 256Mi          limits:            cpu: "2"            memory: 1Gi        volumeMounts:        - name: rclone-config          mountPath: /config/rclone          readOnly: true        - name: scripts          mountPath: /scripts          readOnly: true      volumes:      - name: rclone-config        configMap:          name: rclone-migration-config          items:          - key: rclone.conf            path: rclone.conf      - name: scripts        configMap:          name: rclone-migration-config          items:          - key: migrate.sh            path: migrate.sh            mode: 0755

    Apply the Job:

    Shell
    kubectl config set-context --current --namespace=fossakubectl apply -f rclone-migration-minio-seaweedfs.yaml
  5. 5

    Follow the migration progress

    Shell
    kubectl logs jobs/rclone-minio-to-seaweedfs --follow
  6. 6

    Point your values file at SeaweedFS

    Update fossa-core-config.yml with the SeaweedFS endpoints, iam.region, and iam.kind:

    YAML
    iam:  region: seaweedfs  kind: AccessKey storage:  forcePathStyle: true  endpoint: http://core-seaweedfs-s3:8333  bucket: fossa.test  auth:    type: AccessKey    accessKey: seaweedfs    secretKey: seaweedfs123 hubble:  iam:    region: seaweedfs    kind: AccessKey   storage:    endpoint: http://hubble-seaweedfs-s3:8333    bucket: hubble.fossa.test    auth:      type: AccessKey      accessKey: seaweedfs      secretKey: seaweedfs123
  7. 7

    Cut over and disable maintenance mode

    Once the data migration completes, upgrade the deployment to use SeaweedFS and turn off maintenance mode:

    Shell
    helm upgrade -i fossa fossa/fossa-core --values fossa-core-config.yml --set global.maintenanceMode.enabled=false --version "^5.0.0"
  8. 8

    Verify your data

    Log in to the console and confirm your data is still available.

  9. 9

    Decommission MinIO

    After a week or two of healthy operation, delete the MinIO deployments:

    Shell
    helm delete fossa-core-miniohelm delete fossa-hubble-minio

    If you're certain the MinIO data is no longer needed, delete its PVCs:

    Shell
    kubectl delete pvc fossa-core-miniokubectl delete pvc fossa-hubble-minio
© 2026 FOSSA, Inc.support@fossa.com