Skip to main content
Run bunny skills install --global so AI coding agents, such as Claude Code, Cursor, and Codex, can run this migration for you. Then ask your agent: “Migrate my AWS S3 videos to Bunny Stream.”
bunny stream import is in Public Preview and still in development. Commands, flags, and output may change, so what you see can differ from the examples in this guide. To report a bug, open an issue on the Bunny CLI repository.
Bunny Stream bills only for the storage and bandwidth you use. See the Bunny Stream pricing page for current rates.

Overview

Diagram of the Bunny CLI listing S3 objects and signing their URLs, and Bunny Stream fetching each file directly from the S3 bucket
The Bunny CLI lists every video file in your bucket, creates a pre-signed download URL for each one, and hands it to Bunny Stream, which fetches the file directly from S3. Nothing is downloaded to your machine, and your bucket can stay private. Each top-level folder becomes a Bunny Stream collection with the same name. Each imported video is tagged with a hash of its bucket and key under the s3Source metatag, so running the import again skips videos that are already imported. S3 has no video catalog, so the CLI works out titles and collections from the object keys. For example, a bucket with one video at the top level and two folders maps like this:
Folders nested deeper than the top level are not separate collections. A file at Webinars/2024/Q3 product update.mp4 also lands in the Webinars collection.

Scope

The import copies the video files, their titles, any description and tags in the object metadata, and your top-level folder structure. Access controls stay on AWS, so plan to set up their Bunny equivalents.
Private S3 videos are not private on Bunny. Your bucket can stay private during the import, because Bunny fetches each file through a pre-signed URL. On Bunny, though, every imported video follows the security settings of its library, and a new library allows playback from any site. A video that only signed-in users could reach on S3 or CloudFront plays for anyone with its URL until you protect the library. Before you publish the new URLs, set up Allowed domains, and turn on embed view token authentication or CDN token authentication for videos that only signed-in viewers should see. If those videos are in their own top-level folders, you can import those folders into a separate library with --folder and give that library stricter settings.

Prerequisites

  • A bunny.net account
  • An AWS account with an S3 bucket that holds your videos, and permission to create an IAM user

Migrate your videos

1

Install and authenticate the Bunny CLI

Install the Bunny CLI with the shell installer or npm:
The installer downloads a prebuilt binary for your platform and places it on your PATH.
See Installation for more options. Then authenticate the CLI with your bunny.net account:
bunny login opens your browser to complete authentication and stores the credentials locally. The first time you log in from an interactive terminal, it also offers to install the agent skill for AI coding tools. On a remote machine or in CI without a browser, run bunny login --api-key "$BUNNYNET_API_KEY" instead, with a key from Account Settings > API key.
2

Create a Bunny Stream library

Create a library for the imported videos, or skip this step to use an existing one:
DE is always the main storage region. Replicate to the regions closest to your audience, from UK, SE, NY, LA, SG, SYD, BR, and JH. You can’t remove a replication region later.
Set encoding options, such as --resolutions or --encoding-tier, when you create the library. Changes you make later apply only to new uploads, not to videos that are already imported. Run bunny stream libraries create --help to see every option, or read Encoding.
When the CLI asks how to add videos, choose Later. You start the import in the next steps.Copy the library ID from the output. You pass it to --library in the import commands. The flag also accepts the library’s name, and --lib works as a short alias.
3

Create an IAM user with read access to your bucket

The Bunny CLI needs an AWS access key that can list your bucket and read its objects. It never changes anything in S3.In the IAM console, go to Users and click Create user. Enter a user name, such as migrate-to-bunny, leave console access off, and click Next.
The Specify user details step of Create user in the AWS IAM console with the user name migrate-to-bunny
On Set permissions, choose Attach policies directly, then click Create policy. In the policy editor, switch to JSON and paste the following policy, replacing migrate-to-bunny-demo with your bucket name in both places:
Name the policy, such as migrate-to-bunny-demo, and create it. Back on Set permissions, refresh the policy list, select the new policy, then click Next and Create user.
If your bucket encrypts objects with SSE-KMS, also allow kms:Decrypt on the bucket’s KMS key. Buckets with the default SSE-S3 encryption need nothing more.
Open the new user, go to Security credentials, and click Create access key. Choose Command Line Interface (CLI) as the use case, confirm the recommendation, and create the key. Copy the Access key ID and the Secret access key. AWS shows the secret only once.Set the access key in the shell where you run the import:
The CLI does not prompt for or save access keys. If you already use the AWS CLI, it also picks up keys from ~/.aws/credentials and the profile AWS_PROFILE names. Pre-signed URLs made from temporary credentials, such as an AWS SSO session, stop working when the session ends, so use an access key for a large import.
4

Preview the migration with a dry run

Run the import with --dry-run, using the library ID from step 2:
--dry-run prints the import plan without making any changes to either platform.The CLI then prompts for your bucket’s region and name. The region prompt is filled in with us-east-1, so replace it with your bucket’s region, which the S3 console shows in the bucket list:
Once S3 accepts them, the CLI offers to save them so later runs skip these prompts. Press Enter to accept:
The CLI saves the region and bucket for the active Bunny CLI profile in ~/.config/bunnynet/stream-import-credentials.json (or under $XDG_CONFIG_HOME/bunnynet/ when it is set). Your access keys are not saved. The AWS_REGION and S3_BUCKET environment variables, and the --bucket flag, always take priority over saved values.
The dry run then prints a summary and the import plan:
Already imported counts videos that are already in the library, and To import counts the ones a real run would import. S3 does not know how long a video is, so the summary has no Duration row. It does not report resolution either, so unlike other sources, the dry run cannot warn about videos above Bunny’s 2160p or 72-hour limits. Those videos fail when Bunny encodes them.The plan groups videos by the collection they will land in, and lists them in alphabetical order of their keys, the order S3 returns them in. Videos at the top level of the bucket are listed under (no collection).To import only part of a bucket, add --prefix with the path to start from, such as --prefix videos/2024, or set S3_PREFIX. The top-level folders below that prefix then become the collections.
5

Run the import

Run the same command without --dry-run:
The CLI prints the same summary, then asks for confirmation. Type y and press Enter to start:
The CLI creates a collection for each top-level folder, skips videos that are already imported, hands the rest to Bunny three at a time, and tags each video with s3Source, along with any description and tags from its metadata. It returns once every video is queued. Bunny then fetches each file directly from S3 and encodes it.
Collection names match without regard to case, so the S3 folders Webinars/ and webinars/ land in one collection, even though S3 treats them as separate folders. Move the objects in one of them to a differently named folder before the import to keep them separate.Each pre-signed URL stays valid for six hours by default, which is long enough for Bunny to fetch the file. Set S3_PRESIGNED_URL_TTL to a number of seconds between 3600 and 604800 to change it.The import command also accepts these flags:
  • --wait keeps the command running, with a live encoding progress indicator, until Bunny has encoded every video.
  • --force skips the confirmation prompt and the offer to retry failed videos, for unattended or CI runs.
  • --concurrency N sets how many videos the CLI passes to Bunny in parallel, from 1 to 20.
  • --clean-failed deletes the failed Bunny copies of videos before importing them again, including videos that Bunny rejected.
  • --retry-stalled deletes and imports again any video stuck at zero bytes on Bunny.
  • --request-timeout sets how many seconds one request to S3 or Bunny can take before it gives up (default 30).
  • --video-timeout sets how many seconds the hand-off of one video to Bunny, or with --wait its encoding, can take before it gives up (default 5400).
Run bunny stream import --help for the full list.To migrate a large bucket one folder at a time, list the folders:
The ID of a folder is its path in the bucket. Preview a folder’s videos with --folder, then pass the same ID to the import. Quote the ID when it contains spaces:
The import works the same way, limited to that folder’s videos and its collection. --folder also accepts a deeper path, such as "Webinars/2024". Its videos still land in the top-level collection, Webinars.
6

Watch encoding progress

The import returns before Bunny finishes encoding. Check progress with the status command:
It prints one row per queued video, with its status on Bunny, encode percentage, size, and when it was queued:
Run it again until the last line reports 0 processing. The command exits with a non-zero code when any video has failed, so you can also use it as a check in scripts.
7

Verify in the bunny.net dashboard

Open the bunny.net dashboard and go to Stream > Your library > Manage library. Each top-level S3 folder appears as a collection, and every migrated video is listed under Latest videos.
The Manage library page in the bunny.net dashboard showing the imported collections and videos
Set up the library’s security settings if you have not already, then replace the S3 or CloudFront video URLs in your site with their Bunny Stream equivalents. See Embedding videos for the embed and player options.Once the migration is complete, you can delete the IAM user’s access key, or the user itself.

Troubleshooting

When videos fail during an interactive import, the CLI lists each failure and offers to retry them:
Answer y to retry them in the same run. If the import was interrupted, or you declined the retry, run it again with --resume:
--resume retries failed videos, processes any that were still outstanding, and re-applies the s3Source tag to videos Bunny fetched before the run stopped. When the interrupted run was limited to one folder, the resume continues that folder. It stops retrying a video after three failed attempts and skips videos that Bunny rejected. To retry those too, add --clean-failed, which deletes their Bunny copies first.Running the import again is always safe. Videos that already carry the s3Source tag are skipped, and the CLI reports Everything is already imported once nothing is left.
The region you entered, or the default us-east-1, is not the bucket’s region. Find the region in the S3 console’s bucket list, then run the command again with it set:
AWS_REGION takes priority over a saved region. To replace the saved one, answer yes when the CLI offers to save again.
The access key works, but its policy does not cover this bucket. Check that the bucket name in the IAM policy from step 3 matches your bucket exactly, in both Resource lines.
AWS rejected the access key, or the CLI found no credentials at all. Check that AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are set in the shell where you run the import, and that the key is still active in the IAM console.
The object is in an archive storage class, which S3 cannot serve until it is restored. Restore it from the S3 console, wait for the restore to finish, then run the import again. Until then, the error says the restore is still in progress.
If bunny stream import status reports videos stuck at zero bytes, run the import again with --retry-stalled to delete and fetch them again.

Migrate from an S3-compatible provider

The s3 source also works with S3-compatible storage, such as Cloudflare R2, Wasabi, Backblaze B2, or MinIO. Set S3_ENDPOINT to the provider’s https:// endpoint, and use the provider’s region name, such as auto for R2:
Use an access key from the provider with read access to the bucket. Everything else in this guide works the same way.
Last modified on November 5, 2026