Skip to content

SeaweedFS (Bootstrap) Installation

SeaweedFS (Bootstrap) is the minimal instance required to break the circular dependency between storage and the database. It provides the initial S3 endpoint for CloudnativePG.

  1. Connect to 🟢 Management Kubernetes Cluster ; _i.e w/ Kubeconfig File.

    Set Kubeconfig File

    Ensure you have defined and loaded your Global Shell Variables as described in Shell Variables.

    Terminal window
    source $HOME/opstella-installation/shell-values/kubernetes/management_cluster.vars.sh
    Terminal window
    export KUBECONFIG="$HOME/opstella-installation/kubeconfigs/management_cluster.yaml"
  2. Export Required Shell Variables

    You will need to export several variables that are used within the Helm values and Kubernetes manifest files.

    Ensure K8S_INGRESS_TLS_CERTIFICATE_SECRET_NAME and K8S_STORAGE_CLASS_NAME are defined as per the Shell Variables guide.

    Terminal window
    # S3 Credentials
    export SEAWEEDFS_BOOTSTRAP_S3_ADMIN_PASSWORD="CHANGEME"
    export SEAWEEDFS_BOOTSTRAP_S3_POSTGRES_BACKUP_PASSWORD="CHANGEME"
    # Domain Names
    export SEAWEEDFS_BOOTSTRAP_FILER_DOMAIN="seaweedfs-bootstrap.${BASE_DOMAIN}"
    export SEAWEEDFS_BOOTSTRAP_API_DOMAIN="seaweedfs-bootstrap-s3.${BASE_DOMAIN}"
    export SEAWEEDFS_BOOTSTRAP_ADMIN_DOMAIN="seaweedfs-bootstrap-admin.${BASE_DOMAIN}"
    # Admin Credentials
    export SEAWEEDFS_BOOTSTRAP_ADMIN_PASSWORD="CHANGEME"
  3. Create Namespace for SeaweedFS Bootstrap

    Terminal window
    kubectl create namespace seaweedfs-bootstrap
  4. Create SeaweedFS S3 Configuration

    Terminal window
    cat <<EOF > $HOME/opstella-installation/kubernetes-manifests/seaweedfs-bootstrap-s3.yaml
    ---
    # S3 Credentials for SeaweedFS (Minimal/Bootstrap)
    apiVersion: v1
    kind: Secret
    type: Opaque
    metadata:
    name: seaweedfs-s3-secret
    namespace: seaweedfs-bootstrap
    stringData:
    # ----------------------------------------------------------------------------
    # INPUT CONFIGURATION (YAML)
    # Run 'scripts/seaweedfs-utils.sh update <this_file>' to generate the JSON below.
    # ----------------------------------------------------------------------------
    s3_config_input: |
    users:
    - name: admin
    password: "${SEAWEEDFS_BOOTSTRAP_S3_ADMIN_PASSWORD}"
    actions:
    - Admin
    - Read
    - Write
    - List
    - Tagging
    - name: postgres-backup
    password: "${SEAWEEDFS_BOOTSTRAP_S3_POSTGRES_BACKUP_PASSWORD}"
    actions:
    - "Read:postgres-backups"
    - "Write:postgres-backups"
    - "List:postgres-backups"
    - "Tagging:postgres-backups"
    # ----------------------------------------------------------------------------
    # GENERATED CONFIGURATION (JSON) - DO NOT EDIT MANUALLY
    # ----------------------------------------------------------------------------
    seaweedfs_s3_config: ""
    EOF
    Terminal window
    # Generate proper JSON formatted config into the configuration file
    $HOME/opstella-installation/assets/scripts/seaweedfs-utils.sh update kubernetes-manifests/seaweedfs-bootstrap-s3.yaml

    Apply the configuration:

    Terminal window
    kubectl apply -f seaweedfs-bootstrap-s3.yaml
  5. Add SeaweedFS Helm Repository

    Terminal window
    helm repo add seaweedfs https://seaweedfs.github.io/seaweedfs/helm
    helm repo update
  6. Create SeaweedFS Bootstrap Helm Values

    Terminal window
    cat <<EOF > $HOME/opstella-installation/helm-values/seaweedfs-bootstrap-values.yaml
    global:
    imageName: chrislusf/seaweedfs
    loggingLevel: 1
    enableSecurity: false
    # Disable global replication (Relies on Longhorn)
    enableReplication: false
    # "000" = No replication at SeaweedFS level
    replicationPlacement: "000"
    master:
    enabled: true
    replicas: 1
    # Default volume size is 1GB (1000MB) in this chart, fitting ~10 volumes in 10Gi
    volumeSizeLimitMB: 1000
    # Security Context
    podSecurityContext:
    enabled: true
    fsGroup: 1000
    containerSecurityContext:
    enabled: true
    runAsUser: 1000
    runAsGroup: 1000
    runAsNonRoot: true
    privileged: false
    allowPrivilegeEscalation: false
    seccompProfile:
    type: RuntimeDefault
    capabilities:
    drop: ["ALL"]
    # Persistence for Master (Metadata/Sequence)
    data:
    type: "persistentVolumeClaim"
    size: "5Gi"
    storageClass: "${K8S_STORAGECLASS_NAME}"
    logs:
    type: "emptyDir"
    size: "1Gi"
    volume:
    enabled: true
    replicas: 1
    ipBind: "0.0.0.0"
    minFreeSpacePercent: 5
    # Security Context
    podSecurityContext:
    enabled: true
    fsGroup: 1000
    containerSecurityContext:
    enabled: true
    runAsUser: 1000
    runAsGroup: 1000
    runAsNonRoot: true
    privileged: false
    allowPrivilegeEscalation: false
    seccompProfile:
    type: RuntimeDefault
    capabilities:
    drop: ["ALL"]
    # Persistence for Volume (Data)
    dataDirs:
    - name: data
    type: "persistentVolumeClaim"
    size: "10Gi"
    storageClass: "${K8S_STORAGECLASS_NAME}"
    maxVolumes: 10
    extraArgs: []
    filer:
    enabled: true
    replicas: 1
    port: 9001 # MinIO Console compatibility
    # Security Context
    podSecurityContext:
    enabled: true
    fsGroup: 1000
    containerSecurityContext:
    enabled: true
    runAsUser: 1000
    runAsGroup: 1000
    runAsNonRoot: true
    privileged: false
    allowPrivilegeEscalation: false
    seccompProfile:
    type: RuntimeDefault
    capabilities:
    drop: ["ALL"]
    # Persistence for Filer (Metadata)
    data:
    type: "persistentVolumeClaim"
    size: "5Gi"
    storageClass: "${K8S_STORAGECLASS_NAME}"
    logs:
    type: "emptyDir"
    size: "1Gi"
    # Ingress for Filer (SeaweedFS WebUI)
    ingress:
    enabled: false
    className: nginx
    host: "${SEAWEEDFS_BOOTSTRAP_FILER_DOMAIN}"
    path: "/"
    pathType: Prefix
    annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    ingress.kubernetes.io/proxy-body-size: "0"
    # Basic Auth Configuration
    # nginx.ingress.kubernetes.io/auth-type: basic
    # nginx.ingress.kubernetes.io/auth-secret: seaweedfs-filer-basic-auth
    # nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
    tls:
    - secretName: "${K8S_INGRESS_TLS_CERTIFICATE_SECRET_NAME}"
    hosts:
    - "${SEAWEEDFS_BOOTSTRAP_FILER_DOMAIN}"
    s3:
    enabled: true
    replicas: 1
    port: 9000 # MinIO API compatibility
    enableAuth: true
    existingConfigSecret: seaweedfs-s3-secret
    # Security Context
    podSecurityContext:
    enabled: true
    fsGroup: 1000
    containerSecurityContext:
    enabled: true
    runAsUser: 1000
    runAsGroup: 1000
    runAsNonRoot: true
    privileged: false
    allowPrivilegeEscalation: false
    seccompProfile:
    type: RuntimeDefault
    capabilities:
    drop: ["ALL"]
    logs:
    type: "emptyDir"
    size: "1Gi"
    # Ingress for S3 API
    ingress:
    enabled: true
    className: nginx
    host: "${SEAWEEDFS_BOOTSTRAP_API_DOMAIN}"
    path: "/"
    pathType: Prefix
    annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    ingress.kubernetes.io/proxy-body-size: "0"
    tls:
    - secretName: "${K8S_INGRESS_TLS_CERTIFICATE_SECRET_NAME}"
    hosts:
    - "${SEAWEEDFS_BOOTSTRAP_API_DOMAIN}"
    admin:
    enabled: true
    secret:
    adminUser: "admin"
    adminPassword: "${SEAWEEDFS_BOOTSTRAP_ADMIN_PASSWORD}"
    ingress:
    enabled: true
    className: nginx
    host: "${SEAWEEDFS_BOOTSTRAP_ADMIN_DOMAIN}"
    path: "/"
    pathType: Prefix
    tls:
    - secretName: "${K8S_INGRESS_TLS_CERTIFICATE_SECRET_NAME}"
    hosts:
    - "${SEAWEEDFS_BOOTSTRAP_ADMIN_DOMAIN}"
    # Security Context
    podSecurityContext:
    enabled: true
    fsGroup: 1000
    containerSecurityContext:
    enabled: true
    runAsUser: 1000
    runAsGroup: 1000
    runAsNonRoot: true
    privileged: false
    allowPrivilegeEscalation: false
    seccompProfile:
    type: RuntimeDefault
    capabilities:
    drop: ["ALL"]
    EOF
  7. Install SeaweedFS Bootstrap Helm Release

    Terminal window
    helm upgrade --install seaweedfs-bootstrap seaweedfs/seaweedfs \
    --version 4.0.407 \
    --namespace seaweedfs-bootstrap \
    -f $HOME/opstella-installation/helm-values/seaweedfs-bootstrap-values.yaml
  8. Provision SeaweedFS Buckets

    Terminal window
    cat <<EOF > $HOME/opstella-installation/kubernetes-manifests/seaweedfs-bootstrap-job-provisioning.yaml
    apiVersion: batch/v1
    kind: Job
    metadata:
    name: seaweedfs-bootstrap-provisioning
    namespace: seaweedfs-bootstrap
    labels:
    app.kubernetes.io/name: seaweedfs-bootstrap
    app.kubernetes.io/component: provisioning
    spec:
    ttlSecondsAfterFinished: 300
    template:
    metadata:
    labels:
    app.kubernetes.io/name: seaweedfs-bootstrap
    app.kubernetes.io/component: provisioning
    spec:
    restartPolicy: OnFailure
    # Security Context
    securityContext:
    runAsNonRoot: true
    runAsUser: 10000
    fsGroup: 10000
    seccompProfile:
    type: RuntimeDefault
    containers:
    - name: provisioner
    image: chrislusf/seaweedfs:4.07
    # Security Context
    securityContext:
    allowPrivilegeEscalation: false
    readOnlyRootFilesystem: false
    runAsNonRoot: true
    runAsUser: 10000
    capabilities:
    drop: ["ALL"]
    # Resource Limits
    resources:
    requests:
    memory: "64Mi"
    cpu: "100m"
    limits:
    memory: "128Mi"
    cpu: "200m"
    env:
    # Target SeaweedFS Master (Minimal/Bootstrap instance)
    - name: WEED_MASTER
    value: "seaweedfs-master:9333"
    # Target SeaweedFS Filer (Minimal/Bootstrap instance)
    - name: WEED_FILER
    value: "seaweedfs-filer:9001"
    # Comma-separated list of buckets to create
    - name: SEAWEEDFS_BUCKETS
    value: "postgres-backup"
    command:
    - "/bin/sh"
    - "-c"
    - |
    set -e
    echo "[INFO] Starting SeaweedFS Bootstrap Provisioning..."
    echo "[INFO] Target Master: \$WEED_MASTER"
    echo "[INFO] Target Filer: \$WEED_FILER"
    echo "[INFO] Buckets: \$SEAWEEDFS_BUCKETS"
    # 1. Wait for Master Leader
    echo "[INFO] Waiting for SeaweedFS Master Leader..."
    START_TIME=\$(date +%s)
    TIMEOUT=300
    until echo cluster.ps | weed shell -master=\$WEED_MASTER > /dev/null 2>&1; do
    CURRENT_TIME=\$(date +%s)
    ELAPSED_TIME=\$((\$CURRENT_TIME - \$START_TIME))
    if [ \$ELAPSED_TIME -gt \$TIMEOUT ]; then
    echo "[ERROR] Timeout waiting for SeaweedFS Master at \$WEED_MASTER"
    exit 1
    fi
    echo "[WAIT] Connecting to master... (\${ELAPSED_TIME}s)"
    sleep 5
    done
    echo "[INFO] Connected to Master."
    # 2. Wait for Filer (required for bucket creation)
    echo "[INFO] Waiting for SeaweedFS Filer..."
    until echo fs.ls / | weed shell -master=\$WEED_MASTER -filer=\$WEED_FILER > /dev/null 2>&1; do
    CURRENT_TIME=\$(date +%s)
    ELAPSED_TIME=\$((\$CURRENT_TIME - \$START_TIME))
    if [ \$ELAPSED_TIME -gt \$TIMEOUT ]; then
    echo "[ERROR] Timeout waiting for SeaweedFS Filer at \$WEED_FILER"
    exit 1
    fi
    echo "[WAIT] Connecting to filer... (\${ELAPSED_TIME}s)"
    sleep 5
    done
    echo "[INFO] Connected to Filer."
    # 3. Create Buckets
    # Generate weed shell script
    SCRIPT_FILE="/tmp/provision.weed"
    echo "# Auto-generated provisioning script" > \$SCRIPT_FILE
    # POSIX compliant way to split comma-separated string
    echo "\$SEAWEEDFS_BUCKETS" | tr ',' '\n' | while read -r bucket; do
    # Trim whitespace
    bucket=\$(echo "\$bucket" | sed 's/^[[:space:]]*//;s/[[:space:]]*\$//')
    if [ -n "\$bucket" ]; then
    echo "s3.bucket.create -name \$bucket" >> \$SCRIPT_FILE
    echo "[INFO] Added bucket creation command for: \$bucket"
    fi
    done
    echo "s3.bucket.list" >> \$SCRIPT_FILE
    # Execute
    echo "[INFO] Executing provisioning commands:"
    cat \$SCRIPT_FILE
    echo "----------------------------------------"
    # Run weed shell with better error handling
    OUTPUT_FILE="/tmp/output.log"
    if ! cat \$SCRIPT_FILE | weed shell -master=\$WEED_MASTER -filer=\$WEED_FILER 2>&1 | tee \$OUTPUT_FILE; then
    # Check if error is just "bucket already exists"
    if grep -qi "already exist\|AlreadyExists" \$OUTPUT_FILE; then
    echo "[WARN] Some buckets already exist (expected, continuing...)"
    else
    echo "[ERROR] Bucket creation failed with unexpected error:"
    cat \$OUTPUT_FILE
    exit 1
    fi
    fi
    echo "[INFO] Bucket creation completed."
    # 4. Verify bucket creation
    echo "[INFO] Verifying bucket creation..."
    BUCKET_LIST=\$(echo s3.bucket.list | weed shell -master=\$WEED_MASTER -filer=\$WEED_FILER 2>&1)
    echo "\$SEAWEEDFS_BUCKETS" | tr ',' '\n' | while read -r bucket; do
    bucket=\$(echo "\$bucket" | sed 's/^[[:space:]]*//;s/[[:space:]]*\$//')
    if [ -n "\$bucket" ]; then
    if echo "\$BUCKET_LIST" | grep -q "\$bucket"; then
    echo "[SUCCESS] Bucket '\$bucket' exists"
    else
    echo "[ERROR] Bucket '\$bucket' NOT found!"
    echo "[DEBUG] Available buckets:"
    echo "\$BUCKET_LIST"
    exit 1
    fi
    fi
    done
    echo "[SUCCESS] All buckets verified successfully!"
    EOF

    Apply the provisioning job:

    Terminal window
    kubectl apply -f seaweedfs-bootstrap-job-provisioning.yaml
  1. Verify Pod Status

    Terminal window
    kubectl get pods -n seaweedfs-bootstrap

    💡 All components should be Running:

    NAME READY STATUS RESTARTS AGE
    seaweedfs-admin-0 1/1 Running 0 ...
    seaweedfs-filer-0 1/1 Running 0 ...
    seaweedfs-master-0 1/1 Running 0 ...
    seaweedfs-s3-XXXXXXXXXX-YYYYY 1/1 Running 0 ...
    seaweedfs-volume-0 1/1 Running 0 ...
    seaweedfs-bootstrap-provisioning 0/1 Completed 0 ...
  2. Verify Admin UI and Buckets

    • Access the SeaweedFS Bootstrap Admin UI at https://${SEAWEEDFS_BOOTSTRAP_ADMIN_DOMAIN}.
    • Login with the admin credentials defined in ${SEAWEEDFS_BOOTSTRAP_ADMIN_PASSWORD}.
    • Navigate to the Buckets tab and confirm that the postgres-backup bucket has been successfully created.

Finished?

Use the below navigation to proceed