Automate B2 Storage in AI/ML Pipelines with the Backblaze B2 GitHub Action
  • 26 Aug 2026
  • Dark
    Light

Automate B2 Storage in AI/ML Pipelines with the Backblaze B2 GitHub Action

  • Dark
    Light

Article summary

AI and ML workflows generate a lot of data that needs to move reliably between machines: training datasets, model checkpoints, fine-tuned weights, evaluation outputs, generated media. Without a storage step baked into your CI pipeline, that data either lives only on the runner (gone when the job ends), gets committed to the repo (too large, wrong tool), or requires manual transfers that break reproducibility.

The Backblaze B2 Cloud Storage GitHub Action (backblaze-labs/b2-action) gives you a single, Backblaze-maintained step that handles all of that storage work directly inside GitHub Actions workflows with no CLI installation, no Docker container, and no wrapper scripts.

Built on the backblaze-labs/b2-sdk-typescript and running on Node 24, this action covers 13 operations: upload, download, sync, copy, delete, purge, list, hide, unhide, verify, presign, head, and retention. The action is Backblaze-maintained and currently incubating in Backblaze Labs. This guide was reviewed against v1.2.0.

  • Streaming uploads: Files are never fully buffered in RAM, so multi-GB checkpoints and datasets transfer without memory issues on standard runners.

  • Server-side copy: Promoting a model from a staging bucket to production never routes bytes through the runner.

  • Structured outputs: Each step emits typed outputs, including file IDs, file names, byte counts, per-verb file counts, and summary-json manifests, that downstream steps can consume.

  • Secret-safe: App keys, auth tokens, and presigned URLs are automatically masked in logs.

Prerequisites

Make sure the following prerequisites are in place before you begin.

Set Up Credentials

GitHub Actions runners do not have access to your Backblaze account by default. You need to store your application key as a GitHub secret so the action can authenticate without exposing credentials in your workflow file.

  1. In your GitHub repository, go to Settings > Secrets and variables > Actions.

  2. Click New repository secret and add B2_APPLICATION_KEY_ID with your keyID value.

  3. Add a second secret named B2_APPLICATION_KEY with your applicationKey value.

Reference them in any workflow as ${{ secrets.B2_APPLICATION_KEY_ID }} and ${{ secrets.B2_APPLICATION_KEY }}. Never paste key values directly into a workflow file.

Add the Action to a Workflow

GitHub Actions workflows are YAML files that live in .github/workflows/ in your repository. Each workflow contains one or more jobs, and each job contains steps. The b2-action is a step you add anywhere in a job. The only required inputs are action (which operation to run) and bucket (which Backblaze B2 bucket to use). Everything else depends on the operation.

Here is a minimal example that uploads a file:

- uses: backblaze-labs/b2-action@v1
  with:
    action: upload
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: ./checkpoints/epoch-10.pt
    destination: runs/${{ github.run_id }}/epoch-10.pt

When the workflow runs, you will see a summary table in the Actions UI with the outcome, file ID, and bytes transferred.

Pin the Action for Production Use

The action follows semantic versioning. The version reference you use in uses: controls how stable and auditable your workflow is. It is worth choosing deliberately before deploying to production.

Choose one of the following reference styles based on how your workflow will be used.

Reference style

Behavior

When to use it

@v1

Tracks the latest 1.x release. Picks up patches automatically, but the tag is mutable, meaning the code that runs can change without any change to your workflow file.

Experimentation and development

@vX.Y.Z

A pinned, SSH-signed release tag that shows as Verified on GitHub. Stable and easy to audit, but a Git tag is still a movable ref.

Staging and controlled production

@<full-commit-sha>

Fully immutable. Guarantees the exact code that runs cannot change, even if a tag is moved. If you enable Dependabot for GitHub Actions, it keeps the SHA current automatically.

Production (recommended)

To pin to a full commit SHA:

# Pin to a specific commit SHA for full immutability.
# Dependabot will bump this automatically if enabled.
- uses: backblaze-labs/b2-action@<full-commit-sha> # v1.x.x

Common Tasks

The following tasks cover the most common AI/ML storage patterns. Add whichever steps apply to your workflow.

Save a Model Checkpoint After Training

Training jobs on ephemeral CI runners lose all local files when the job ends. Uploading checkpoints to Backblaze B2 during or after training means you can resume from a known state, compare runs, and roll back to an earlier version, all without keeping a runner alive.

At the end of your training job, add the following step to your workflow YAML. Replace my-ml-bucket with your bucket name:

- uses: backblaze-labs/b2-action@v1
  with:
    action: upload
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: ./checkpoints/epoch-10.pt
    destination: runs/${{ github.run_id }}/checkpoints/epoch-10.pt

To upload an entire checkpoint directory instead of a single file, point source at the folder:

- uses: backblaze-labs/b2-action@v1
  with:
    action: upload
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: ./checkpoints
    destination: runs/${{ github.run_id }}/checkpoints/

Using ${{ github.run_id }} in the destination path keeps each run's artifacts in their own prefix, making them easy to find and compare later.

Pull a Dataset or Model Weights Before a Job

Training and fine-tuning jobs need their input data available on the runner before they start. Storing datasets and base model weights in Backblaze B2 gives every job a consistent, versioned starting point, regardless of which runner picks up the job or when it runs.

At the start of your job, before any training or inference steps, add a download step. Replace the source path with the Backblaze B2 key for your dataset or weights file:

- uses: backblaze-labs/b2-action@v1
  with:
    action: download
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: datasets/imagenet-subset.tar.gz
    destination: ./data/imagenet-subset.tar.gz

To download an entire directory of files (for example, all shards of a dataset or all files for a model) add a trailing slash to the source path:

- uses: backblaze-labs/b2-action@v1
  with:
    action: download
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: models/llama-3-finetuned/
    destination: ./models/

Cache a Model or Dataset Across Runs

Downloading large model weights or datasets from scratch on every run wastes time and egress. Caching in Backblaze B2 lets you persist the local runner cache between runs, so subsequent jobs only pull what has changed. This is especially useful for Hugging Face model caches, which can be several GB and contain many small files.

The pattern uses two steps: restore the cache at the start of the job, then save it at the end.

At the start of your job, add a sync step to pull the cache from Backblaze B2 to the runner:

- uses: backblaze-labs/b2-action@v1
  with:
    action: sync
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: caches/${{ runner.os }}/huggingface/
    destination: ~/.cache/huggingface
    direction: down

After your training or inference steps, add a sync step to push any new or changed files back to Backblaze B2:

- uses: backblaze-labs/b2-action@v1
  with:
    action: sync
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: ~/.cache/huggingface
    destination: caches/${{ runner.os }}/huggingface/
    direction: up    keep-mode: delete

sync only transfers files that have changed since the last run, so subsequent runs get progressively faster as the cache stabilizes. keep-mode: delete removes files from Backblaze B2 that no longer exist locally, keeping the remote cache clean.

Promote a Model from Staging to Production

After evaluation, you need to move a validated model from a staging bucket to production. Downloading and re-uploading routes gigabytes of model data through the runner unnecessarily, slowing the pipeline and consuming egress. Server-side copy moves the file entirely within Backblaze B2. The runner just issues the instruction.

Add a copy step to your deployment or promotion workflow. Set bucket to your production bucket, source-bucket to your staging bucket, and update the source and destination paths for your model. The application key must have read access to the source bucket and write access to the destination bucket:

- uses: backblaze-labs/b2-action@v1
  with:
    action: copy
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: models-prod
    source-bucket: models-staging
    source: llama-3-finetuned-v2.pt
    destination: llama-3-finetuned-latest.pt

To copy within the same bucket (for example, to tag the current best checkpoint as latest) omit source-bucket:

- uses: backblaze-labs/b2-action@v1
  with:
    action: copy
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: runs/${{ github.run_id }}/checkpoints/epoch-10.pt
    destination: models/best-checkpoint.pt

Generate a Presigned URL to Share an Artifact

Evaluation reports, generated media, and run summaries stored in a private Backblaze B2 bucket are not directly accessible to people or systems outside your pipeline. A presigned URL creates a time-limited link to a specific file that anyone can access without needing Backblaze B2 credentials. This is useful for Slack notifications, emails, or downstream systems that need to fetch the file.

Add a presign step and give it an id so you can reference the URL in later steps. Set presign-ttl to how long (in seconds) the link should remain valid:

- id: share
  uses: backblaze-labs/b2-action@v1
  with:
    action: presign
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: runs/${{ github.run_id }}/eval-report.html
    presign-ttl: 86400

Reference the URL in a subsequent step using ${{ steps.share.outputs.presigned-url }}. Here is an example that posts it to a Slack webhook:

- name: Post report link to Slack
  env:  
    REPORT_URL: ${{ steps.share.outputs.presigned-url }}
    SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}  
  run: |  
    curl -X POST "$SLACK_WEBHOOK" \  
      -H "Content-Type: application/json" \  
      -d "{\"text\": \"Eval report: $REPORT_URL\"}"
steps.share.outputs.presigned-url }}\"}"

The URL is automatically masked in logs. It will not appear in plaintext even if you echo it directly.

Verify a Checkpoint Was Not Corrupted in Transit

Large file transfers can silently produce a corrupted file that appears complete. For model checkpoints, a corrupted file might load without error but produce wrong outputs. Verifying the SHA-1 digest after upload confirms the remote file is bit-for-bit identical to the local source without downloading the file again.

Upload the checkpoint as normal, giving the step an id:

- id: upload
  uses: backblaze-labs/b2-action@v1
  with:
    action: upload
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: ./checkpoints/epoch-10.pt
    destination: runs/${{ github.run_id }}/epoch-10.pt

Immediately after, add a verify step. Set source to the B2 path and destination to the local file you just uploaded from:

- uses: backblaze-labs/b2-action@v1
  with:
    action: verify
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: runs/${{ github.run_id }}/epoch-10.pt
    destination: ./checkpoints/epoch-10.pt

The verify step sends only a HEAD request to fetch the remote SHA-1, with no body download. The step fails and the workflow stops if the hashes do not match.

Note

Verify requires a whole-file SHA-1 from Backblaze B2. Multipart-uploaded objects may not have one, and the action sets verified to false and fails closed when that happens.

Use Step Outputs Downstream

Action steps produce structured outputs such as B2 file IDs, file names, SHA-1 digests, byte counts, and per-verb counts. The exact outputs vary by verb. These are useful for logging to a model registry, triggering downstream systems, or building audit trails without having to re-query Backblaze B2 for the information.

Give the action step an id:

- id: upload
  uses: backblaze-labs/b2-action@v1
  with:
    action: upload
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: ./checkpoints/epoch-10.pt
    destination: runs/${{ github.run_id }}/epoch-10.pt

In any later step, reference the outputs using ${{ steps.\<id\>.outputs.\<output-name\> }}, and pass them through env before using them in shell commands:  

- name: Record upload metadata
  env:`  
    FILE_ID: ${{ steps.upload.outputs.file-id }}
    FILE_SHA1: ${{ steps.upload.outputs.content-sha1 }}
    FILE_BYTES: ${{ steps.upload.outputs.bytes-transferred }}
  run: |
    echo "File ID : $FILE_ID"
    echo "SHA-1   : $FILE_SHA1"  
    echo "Bytes   : $FILE_BYTES"

See the Backblaze B2 GitHub Action for the full list of available outputs and which verbs produce them.

Clean Up Old Run Artifacts

AI/ML pipelines can accumulate a large volume of run artifacts quickly. Checkpoints from every epoch of every run add up fast. Purging old run data keeps storage costs down and buckets organized, but you want to be careful about what you delete. The dry-run option lets you see exactly what would be removed before committing.

Add a purge step with dry-run: true and run your workflow. Review the step summary in the Actions UI to confirm the list of files that would be deleted:

- uses: backblaze-labs/b2-action@v1
  with:
    action: purge
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: runs/old-run-id/
    dry-run: true

After you have confirmed the file list looks correct, remove dry-run: true and run again to permanently delete.

Note

purge removes all file versions under the prefix, including hide markers and version history. An exact-name delete removes only the latest version of one file, but a prefix delete also removes every version under that prefix.

After you confirm that the file list looks correct, remove dry-run: true and run again to permanently delete.

Encryption

Proprietary model weights and sensitive training datasets may require encryption at rest for compliance, IP protection, or organizational policy. The action supports two modes of server-side encryption with the sse input. Use sse: B2 on uploads for Backblaze-managed encryption, or sse: C:<base64-key> on uploads and downloads for customer-managed encryption (SSE-C).

SSE-B2 (Simplest — No Key Management)

Backblaze generates and manages the encryption key. There is nothing to set up beyond adding sse: B2 to your step. If you lose access to your Backblaze B2 account, Backblaze can still recover the data.

Add sse: B2 to any upload step:

- uses: backblaze-labs/b2-action@v1
  with:
    action: upload
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: ./models/proprietary-weights.pt
    destination: models/proprietary-weights.pt
    sse: B2

SSE-C (You Hold the Key)

You provide a 256-bit encryption key. Backblaze never stores it. This gives you full control over who can decrypt the data, but it means you are solely responsible for keeping the key safe.

Run the following command in your terminal to generate a 256-bit key:

openssl rand -base64 32

Copy the output (a 44-character string like JXqRk7TZUyDhPmlAv9pn0WzgQGkBNyfwHJtoMSCRXNc=) and add it as a GitHub repository secret named B2_SSE_C_KEY_B64.

Reference it in your upload and download steps:

- uses: backblaze-labs/b2-action@v1
  with:
    action: upload
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-ml-bucket
    source: ./models/proprietary-weights.pt
    destination: models/proprietary-weights.pt
    sse: C:${{ secrets.B2_SSE_C_KEY_B64 }}

Important

Backblaze never stores your SSE-C key. You must supply the same key at download time. If you lose the key, the data is permanently unrecoverable; there is no recovery path. The action masks the key in logs automatically, but keep it out of bug reports and issue comments.

Object Lock

Some AI workflows have compliance or legal requirements around data retention. For example, you may need to keep training data provenance records immutable for a set period, or preserve model artifacts that informed a regulated decision. Object Lock lets you write a file to Backblaze B2 and then prevent anyone, including bucket owners, from modifying or deleting it until a specified date.

Object Lock requires a bucket with Object Lock enabled. You can enable this when creating a bucket in the Backblaze console.

Upload the file you want to lock to Backblaze B2 first if it is not already there, then add a retention step. Set retention-mode to compliance or governance, and set retention-until to an ISO 8601 timestamp for when the lock should expire:

- uses: backblaze-labs/b2-action@v1
  with:
    action: retention
    application-key-id: ${{ secrets.B2_APPLICATION_KEY_ID }}
    application-key: ${{ secrets.B2_APPLICATION_KEY }}
    bucket: my-locked-bucket
    source: audits/training-run-2026-q2.tar.gz
    retention-mode: compliance
    retention-until: '2031-04-01T00:00:00Z'
    legal-hold: 'on'

The two retention modes behave differently:

  • compliance: The lock cannot be shortened or removed by anyone, including Backblaze, until the date passes. Use this for regulatory requirements.

  • governance: The lock can be overridden by users with the bypassGovernance capability by setting bypass-governance: true. Use this for internal policy enforcement where you may need an override path.

Reference

This section lists all 13 verbs the action supports, along with the full inputs and outputs reference. Use it to look up available operations and configure steps beyond the common tasks covered above.

All 13 Verbs

Each verb maps to a single Backblaze B2 operation and is set via the action input. The following table lists all 13 and what they do.

Verb

What it does

upload

Upload a file or directory to Backblaze B2

download

Download a file or prefix from Backblaze B2

sync

Mirror a local directory to or from a B2 prefix

copy

Server-side copy within or across buckets

delete

Delete the latest version of a file or prefix

purge

Permanently delete all versions of a file or prefix

list

List files under a prefix; emits JSON for downstream steps

hide

Soft-delete a file (preserves data until lifecycle runs)

unhide

Restore a hidden file

verify

Compare remote vs local SHA-1 (no download)

presign

Generate a time-limited download URL

head

Fetch file metadata only (no download)

retention

Apply Object Lock retention and/or legal hold

Key Inputs

Key Inputs

The following table covers the inputs used most commonly across the tasks in this guide. For the full list of every available input and its accepted values, see the Backblaze B2 GitHub Action guide.

Input

Default

Notes

action

—

Required. One of the 13 verbs above.

application-key-id

—

Falls back to $B2_APPLICATION_KEY_ID env var.

application-key

—

Falls back to $B2_APPLICATION_KEY env var.

bucket

—

Required. Destination bucket (for copy, the destination bucket).

source

—

Local path/glob for upload and sync up; B2 path or prefix for everything else.

destination

—

B2 path/prefix for upload, copy, sync up; local path for download, sync down, verify.

source-bucket

bucket

Source bucket for cross-bucket copy.

dry-run

false

Preview without making changes (sync, delete, purge).

allow-bucket-purge

false

Required for purge when source is empty or /.

presign-ttl

3600

Presigned URL TTL in seconds.

sse

—

B2 for SSE-B2, or C:<base64-key> for SSE-C.

direction

auto

Sync direction: auto, up, or down.

keep-mode

no-delete

Sync orphan policy: no-delete, delete, or keep-days.

keep-days

—

Retention window for keep-mode: keep-days.

expected-sha1

—

Literal SHA-1 for verify when you do not want to hash a local destination file.

retention-mode

—

compliance, governance, or none.

retention-until

—

ISO 8601 expiry timestamp (required for compliance and governance).

legal-hold

—

on or off.

bypass-governance

false

Allows governance-mode retention bypass for retention changes and delete/purge operations when the key has the bypassGovernance capability.

Key Outputs

Every action step emits structured outputs that downstream steps can reference using ${{ steps.<id>.outputs.<output-name> }}. The following table covers the most useful outputs. For the complete list, see the Backblaze B2 GitHub Action guide.

Output

Available on

Description

file-id

upload, copy, hide, retention, head; unhide if a hide marker was removed

B2 file ID of the affected object or removed hide marker.

content-sha1

upload, download, head when available

SHA-1 hex digest. Omitted when Backblaze B2 does not expose a whole-file SHA-1, including multipart objects.

bytes-transferred

upload, download, sync, copy, head

Total bytes moved. head emits 0.

file-count

All verbs

Aggregate count of files matched or processed.

files-uploaded

upload, sync up

Count of files uploaded.

files-downloaded

download, sync down

Count of files downloaded.

files-deleted

delete, purge, sync

Count of files deleted.

presigned-url

presign

Time-limited download URL. Masked in logs.

verified

verify

true or false.

remote-sha1

verify

The remote object's SHA-1.

local-sha1

verify

Local file SHA-1 when computed from destination.

summary-json

All verbs

Complete JSON array with per-file details when it fits within 256 KiB; emits [] when truncated.

summary-json-truncated

All verbs

true when the full summary-json manifest exceeded the supported size cap.

retryable

Failure path

true when the error is safe to retry automatically.

retry-after

Failure path

Retry delay in seconds when retryable is true.

Additional Resources

Help us improve this guide. If you find an error, notice outdated information, or have suggestions for improvement, email techpubs@backblaze.com.


Was this article helpful?