Yandex Cloud
Search
Discuss with expertTry it for free
  • Customer Stories
  • Documentation
  • Blog
  • All Services
    • Cloud Interconnect
    • Cloud Backup
    • Cloud Registry
    • Yandex AI Studio
    • Compute Cloud
    • Object Storage
    • Managed Service for Kubernetes®
    • Yandex BareMetal
    • Smart Web Security
    • Security Deck
    • Managed Service for PostgreSQL
    • Managed Service for ClickHouse®
    • Monium
    • Cloud CDN
    • Network Load Balancer
    • Virtual Private Cloud
    • Cloud DNS
    • Application Load Balancer
    • Yandex Cloud Video
    • Stackland
    • Yandex Cloud Router
    • Yandex Managed Service for Trino
    • Managed Service for MySQL®
    • Managed Service for Valkey™
    • Managed Service for Apache Spark™
    • Yandex StoreDoc
    • Managed Service for OpenSearch
    • Managed Service for Apache Kafka®
    • Data Transfer
    • Yandex MPP Analytics Engine for PostgreSQL
    • Yandex Managed Service for Apache Airflow®
    • Data Processing
    • Yandex MetaData Hub
    • Managed Service for YDB
    • Managed Service for Sharded PostgreSQL
    • Managed Service for YTsaurus
    • Yandex WebSQL
    • DataLens
    • Yandex Search API
    • SpeechSense
    • SpeechKit
    • DataSphere
    • Vision OCR
    • Translate
    • Yandex Identity Hub
    • Key Management Service
    • Certificate Manager
    • Yandex Lockbox
    • Audit Trails
    • SmartCaptcha
    • Yandex SIEM
    • SourceCraft Code Assistant
    • Container Registry
    • Managed Service for GitLab
    • Managed Service for Prometheus®
    • Cloud Functions
    • API Gateway
    • Yandex Cloud Postbox
    • Message Queue
    • IoT Core
    • Data Streams
    • Serverless Containers
    • Cloud Notification Service
    • Identity and Access Management
    • Yandex Cloud Console
    • Resource Manager
    • Yandex Cloud Billing
    • Yandex Cloud Quota Manager
    • Cloud Apps
  • System Status
  • Marketplace
    • Featured
    • Infrastructure & Network
    • Data Platform
    • AI for business
    • Security
    • DevOps tools
    • Serverless
    • Monitoring & Resources
  • All Solutions
    • By industry
    • By use case
    • Economics and Pricing
    • Security
    • Technical Support
    • Start testing with double trial credits
    • Cloud credits to scale your IT product
    • Gateway to Russia
    • Cloud for Startups
    • Center for Technologies and Society
    • Yandex Cloud Partner program
    • Price calculator
    • Pricing plans
  • Customer Stories
  • Documentation
  • Blog
© 2026 Direct Cursus Technology L.L.C.
Yandex Cloud Stackland
  • What's new
  • Installation
    • All tutorials
    • Installing Stackland on Yandex BareMetal
    • Installing Stackland on Yandex BareMetal via PXE
    • Installing Stackland on Yandex Cloud VMs
    • Setting up external access to a pod in a cluster
    • Verifying signatures for custom images
    • All guides
      • Creating a user
      • Creating a user group
      • Connecting a SAML federation
      • Assigning access permissions
      • Creating a YDB backup
      • Restoring a YDB from a backup
    • Projects
    • Resource model
    • Scaling a cluster
    • Licensing
  • Access management
  • Pricing policy
  • Diagnostics and troubleshooting

In this article:

  • Getting started
  • Creating a backup
  • Checking the result
  1. Step-by-step guides
  2. Users and groups
  3. Creating a YDB backup

Creating a YDB backup

Written by
Yandex Cloud
Updated at October 30, 2026
View in Markdown
  • Getting started
  • Creating a backup
  • Checking the result

Using the ydb-backup.sh script, a cluster administrator can create an Identity and Access Management database backup in YDB and save it on a workstation as a tar.gz archive.

The script is supported in Stackland version 26.3 and higher.

Getting startedGetting started

  1. Make sure the workstation (Linux or macOS) has bash, awk, du, kubectl, tar, and mktemp installed.

  2. Prepare a kubeconfig file with admin cluster access. You can use a kubeconfig with the cluster-admin role created when installing the cluster.

  3. Make sure your YDB is accessible and the workstation has enough free space to store both the unpacked data and the archive simultaneously.

  4. Make sure a default StorageClass with file volume support (volumeMode: Filesystem) is configured in the cluster. The script uses this storage class for a temporary PVC with a full data copy. The storage must have enough free space for the PVC.

  5. Specify the absolute path to your kubeconfig file, then check the selected cluster and storage classes:

    KUBECONFIG_PATH="<absolute_path_to_kubeconfig>"
    
    kubectl --kubeconfig "${KUBECONFIG_PATH}" config current-context
    kubectl --kubeconfig "${KUBECONFIG_PATH}" cluster-info
    kubectl --kubeconfig "${KUBECONFIG_PATH}" get storageclass
    

Creating a backupCreating a backup

  1. Save the script below as ydb-backup.sh on your workstation.

    ydb-backup.sh script
    #!/usr/bin/env bash
    
    set -Eeuo pipefail
    
    usage() {
      printf 'Usage: %s [--kubeconfig PATH] [--pvc-size SIZE] [--timeout SECONDS] [OUTPUT_PATH]\n' "$(basename -- "$0")"
    }
    
    kubeconfig_flag=""
    pvc_size="20Gi"
    timeout="3600"
    output_argument=""
    while (( $# > 0 )); do
      case "$1" in
        --kubeconfig)
          (( $# >= 2 )) || {
            printf 'Missing value for --kubeconfig\n' >&2
            exit 2
          }
          kubeconfig_flag="$2"
          shift 2
          ;;
        --kubeconfig=*)
          kubeconfig_flag="${1#*=}"
          shift
          ;;
        --pvc-size)
          (( $# >= 2 )) || {
            printf 'Missing value for --pvc-size\n' >&2
            exit 2
          }
          pvc_size="$2"
          shift 2
          ;;
        --pvc-size=*)
          pvc_size="${1#*=}"
          shift
          ;;
        --timeout)
          (( $# >= 2 )) || {
            printf 'Missing value for --timeout\n' >&2
            exit 2
          }
          timeout="$2"
          shift 2
          ;;
        --timeout=*)
          timeout="${1#*=}"
          shift
          ;;
        -h|--help)
          usage
          exit 0
          ;;
        -*)
          printf 'Unknown option: %s\n' "$1" >&2
          usage >&2
          exit 2
          ;;
        *)
          [[ -z "${output_argument}" ]] || {
            printf 'Only one output path may be specified\n' >&2
            usage >&2
            exit 2
          }
          output_argument="$1"
          shift
          ;;
      esac
    done
    
    if [[ -n "${kubeconfig_flag}" ]]; then
      resolved_kubeconfig_path="${kubeconfig_flag}"
    elif [[ -n "${KUBECONFIG:-}" ]]; then
      resolved_kubeconfig_path="${KUBECONFIG}"
    elif [[ -n "${HOME:-}" ]]; then
      resolved_kubeconfig_path="${HOME}/.kube/config"
    else
      printf 'Cannot resolve kubeconfig: HOME is not set\n' >&2
      exit 2
    fi
    
    readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
    
    readonly YDB_NAMESPACE="stackland-iam"
    readonly YDB_ENDPOINT="grpcs://ydb-storage-grpc.${YDB_NAMESPACE}.svc.cluster.local:2135"
    readonly YDB_DATABASE="/ydb-storage/iam"
    readonly YDB_USER="operator"
    readonly KUBECONFIG_PATH="${resolved_kubeconfig_path}"
    readonly PVC_SIZE="${pvc_size}"
    readonly TIMEOUT="${timeout}"
    readonly BACKUP_TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"
    readonly POD_NAME="ydb-backup"
    readonly PVC_NAME="ydb-backup"
    readonly OUTPUT_PATH="${output_argument:-${SCRIPT_DIR}/ydb-backup-${BACKUP_TIMESTAMP}.tar.gz}"
    
    POD_CREATED=false
    PVC_CREATED=false
    STAGING_DIR=""
    
    log() {
      printf '[ydb-backup] %s\n' "$*"
    }
    
    fail() {
      printf '[ydb-backup] ERROR: %s\n' "$*" >&2
      exit 1
    }
    
    cleanup() {
      local exit_code=$?
      trap - EXIT INT TERM
    
      if [[ "${POD_CREATED}" == true ]]; then
        log "Deleting Pod ${YDB_NAMESPACE}/${POD_NAME}"
        kubectl --kubeconfig "${KUBECONFIG_PATH}" \
          --namespace "${YDB_NAMESPACE}" \
          delete pod "${POD_NAME}" --ignore-not-found --wait=false >/dev/null || true
      fi
    
      if [[ "${PVC_CREATED}" == true ]]; then
        log "Deleting PVC ${YDB_NAMESPACE}/${PVC_NAME}"
        kubectl --kubeconfig "${KUBECONFIG_PATH}" \
          --namespace "${YDB_NAMESPACE}" \
          delete pvc "${PVC_NAME}" --ignore-not-found --wait=false >/dev/null || true
      fi
    
      if [[ -n "${STAGING_DIR}" && -d "${STAGING_DIR}" ]]; then
        rm -rf -- "${STAGING_DIR}"
      fi
    
      exit "${exit_code}"
    }
    
    trap cleanup EXIT
    trap 'exit 130' INT
    trap 'exit 143' TERM
    
    for command_name in kubectl tar mktemp; do
      command -v "${command_name}" >/dev/null 2>&1 || fail "Required command is not installed: ${command_name}"
    done
    
    [[ -f "${KUBECONFIG_PATH}" ]] || fail "Kubeconfig does not exist: ${KUBECONFIG_PATH}"
    [[ "${TIMEOUT}" =~ ^[1-9][0-9]*$ ]] || fail "Timeout must be a positive number of seconds"
    [[ "${PVC_SIZE}" =~ ^[1-9][0-9]*(Ei|Pi|Ti|Gi|Mi|Ki|E|P|T|G|M|K)?$ ]] \
      || fail "PVC size must be a positive Kubernetes quantity, for example 20Gi"
    [[ ! -e "${OUTPUT_PATH}" ]] || fail "Output file already exists: ${OUTPUT_PATH}"
    
    YDB_IMAGE="$(kubectl --kubeconfig "${KUBECONFIG_PATH}" \
      --namespace "${YDB_NAMESPACE}" \
      get statefulset ydb-storage \
      --output jsonpath='{.spec.template.spec.containers[0].image}')"
    [[ -n "${YDB_IMAGE}" ]] || fail "Cannot resolve image from StatefulSet ${YDB_NAMESPACE}/ydb-storage"
    readonly YDB_IMAGE
    
    output_directory="$(dirname -- "${OUTPUT_PATH}")"
    mkdir -p -- "${output_directory}"
    STAGING_DIR="$(mktemp -d "${output_directory}/.ydb-backup.XXXXXX")"
    
    log "Deleting stale Pod ${YDB_NAMESPACE}/${POD_NAME}"
    kubectl --kubeconfig "${KUBECONFIG_PATH}" \
      --namespace "${YDB_NAMESPACE}" \
      delete pod "${POD_NAME}" --ignore-not-found --wait=true --timeout="${TIMEOUT}s" >/dev/null
    
    log "Deleting stale PVC ${YDB_NAMESPACE}/${PVC_NAME}"
    kubectl --kubeconfig "${KUBECONFIG_PATH}" \
      --namespace "${YDB_NAMESPACE}" \
      delete pvc "${PVC_NAME}" --ignore-not-found --wait=true --timeout="${TIMEOUT}s" >/dev/null
    
    log "Creating ${PVC_SIZE} PVC ${YDB_NAMESPACE}/${PVC_NAME} using the default StorageClass"
    PVC_CREATED=true
    kubectl --kubeconfig "${KUBECONFIG_PATH}" apply -f - <<EOF
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: ${PVC_NAME}
      namespace: ${YDB_NAMESPACE}
      labels:
        app.kubernetes.io/name: ydb
        app.kubernetes.io/component: dump-download
    spec:
      accessModes:
        - ReadWriteOnce
      volumeMode: Filesystem
      resources:
        requests:
          storage: ${PVC_SIZE}
    EOF
    
    log "Creating Pod ${YDB_NAMESPACE}/${POD_NAME}"
    POD_CREATED=true
    kubectl --kubeconfig "${KUBECONFIG_PATH}" apply -f - <<EOF
    apiVersion: v1
    kind: Pod
    metadata:
      name: ${POD_NAME}
      namespace: ${YDB_NAMESPACE}
      labels:
        app.kubernetes.io/name: ydb
        app.kubernetes.io/component: dump-download
    spec:
      automountServiceAccountToken: false
      restartPolicy: Never
      securityContext:
        runAsNonRoot: true
        runAsUser: 65532
        runAsGroup: 65532
        fsGroup: 65532
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: backup
          image: ${YDB_IMAGE}
          imagePullPolicy: IfNotPresent
          command:
            - /bin/sleep
            - infinity
          env:
            - name: HOME
              value: /tmp
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
          volumeMounts:
            - name: backup
              mountPath: /backup
            - name: ydb-ca
              mountPath: /credentials/ydb/ca.crt
              subPath: ca.crt
              readOnly: true
            - name: ydb-password
              mountPath: /credentials/ydb/password
              subPath: password
              readOnly: true
      volumes:
        - name: backup
          persistentVolumeClaim:
            claimName: ${PVC_NAME}
        - name: ydb-ca
          secret:
            secretName: ydb-storage-grpc-tls
            items:
              - key: ca.crt
                path: ca.crt
        - name: ydb-password
          secret:
            secretName: ydb-storage-operator-user
            items:
              - key: password
                path: password
    EOF
    
    log "Waiting for Pod ${YDB_NAMESPACE}/${POD_NAME}"
    kubectl --kubeconfig "${KUBECONFIG_PATH}" \
      --namespace "${YDB_NAMESPACE}" \
      wait --for=condition=Ready "pod/${POD_NAME}" --timeout="${TIMEOUT}s" >/dev/null
    
    log "Creating YDB dump in Pod ${POD_NAME}"
    if kubectl --kubeconfig "${KUBECONFIG_PATH}" \
      --namespace "${YDB_NAMESPACE}" \
      exec "${POD_NAME}" --container backup -- \
      timeout "${TIMEOUT}s" \
      /opt/ydb/bin/ydb \
      --ca-file /credentials/ydb/ca.crt \
      -e "${YDB_ENDPOINT}" \
      -d "${YDB_DATABASE}" \
      --user "${YDB_USER}" \
      --password-file /credentials/ydb/password \
      admin database dump \
      --output /backup/database; then
      :
    else
      dump_exit_code=$?
      if (( dump_exit_code == 124 )); then
        fail "YDB dump timed out after ${TIMEOUT} seconds"
      fi
      fail "YDB dump failed with exit code ${dump_exit_code}"
    fi
    
    log "Downloading /backup/database"
    kubectl --kubeconfig "${KUBECONFIG_PATH}" \
      --namespace "${YDB_NAMESPACE}" \
      cp "${POD_NAME}:/backup/database" "${STAGING_DIR}/database" --container backup
    
    # wipe sensitive data
    kubectl --kubeconfig "${KUBECONFIG_PATH}" \
      --namespace "${YDB_NAMESPACE}" \
      exec "${POD_NAME}" --container backup -- \
      timeout "${TIMEOUT}s" /bin/sh -eu -c '
        command -v find >/dev/null
        command -v shred >/dev/null
        if [ -d /backup/database ]; then
          find /backup/database -type f -exec shred --iterations=1 --zero --exact -- {} +
        fi
        sync
      ' >/dev/null 2>&1 || true
    
    log "Creating ${OUTPUT_PATH}"
    tar -C "${STAGING_DIR}/database" -czf "${STAGING_DIR}/backup.tar.gz" .
    tar -tzf "${STAGING_DIR}/backup.tar.gz" >/dev/null \
      || fail "Created backup archive is not readable"
    mv -- "${STAGING_DIR}/backup.tar.gz" "${OUTPUT_PATH}"
    
    archive_size="$(du -h "${OUTPUT_PATH}" | awk '{print $1}')"
    log "Backup completed: ${OUTPUT_PATH} (${archive_size})"
    
  2. Make the file executable:

    chmod +x ydb-backup.sh
    
  3. Specify the path to the new archive and run the script:

    BACKUP_PATH="$(pwd)/ydb-backup-$(date -u +%Y%m%dT%H%M%SZ).tar.gz"
    
    ./ydb-backup.sh \
      --kubeconfig "${KUBECONFIG_PATH}" \
      --pvc-size 20Gi \
      --timeout 3600 \
      "${BACKUP_PATH}"
    

    Where:

    • --kubeconfig: Kubeconfig file path. If not set, the script uses the path from the KUBECONFIG variable, then falls back to $HOME/.kube/config. Specify the path to a single file.
    • --pvc-size: Size of the temporary PVC for the full data copy in the cluster. The default value is 20Gi. If the copy does not fit in the PVC, rerun the script with a larger value for this setting.
    • --timeout: Timeout for individual operations and creating the YDB dump, in seconds. The default value is 3600. This is not a limit on the total duration of the script execution.
    • BACKUP_PATH: Path to the archive. An existing file will not be overwritten. If the path is not specified, the script generates a ydb-backup-<UTC_date_and_time>.tar.gz file in the script’s directory.

    Do not run multiple instances of the script at once in the same cluster: they use the same temporary pod and PVC. After completion, the script deletes the temporary pod and PVC.

  4. Wait for the Backup completed message with the path to the archive and its size.

Checking the resultChecking the result

  1. Make sure the archive is created and readable:

    ls -lh "${BACKUP_PATH}"
    tar -tzf "${BACKUP_PATH}" >/dev/null
    
  2. Save the archive in a secure location separate from the cluster. The archive contains confidential Identity and Access Management data: restrict access to it during storage and transmission.

For information on how to restore the database, see Restoring a YDB from backup.

Was the article helpful?

Previous
Assigning access permissions
Next
Restoring a YDB from a backup
© 2026 Direct Cursus Technology L.L.C.