How to Upload Files to Google Cloud Storage Using gcloud and gsutil

This page covers how to upload, download, sync, and manage files in Google Cloud Storage from the command line. You will use gcloud storage for new work and understand why gsutil still appears throughout existing scripts and documentation. Both tools are covered side by side.

The basics in plain English

Before diving into commands, here is the core model you need to understand:

  • A bucket is the container that holds your files. Think of it as a top-level folder with a globally unique name. Buckets are explained in detail in the Storage Buckets Explained guide.

  • An object is any file stored inside a bucket. Objects do not have a real directory structure, just a flat name that can contain slashes (like reports/2026/summary.pdf) to simulate folders.

  • cp copies a file from one location to another. The source and destination can be local paths or gs:// bucket paths.

  • rsync keeps a source and destination in sync by transferring only what has changed. It is efficient for repeated deployments where most files stay the same.

  • gcloud storage is the modern CLI for Cloud Storage, built into the gcloud CLI you already use for other GCP services.

  • gsutil is the older standalone CLI. It still works but is no longer actively developed. You will encounter it constantly in existing code and tutorials.

gcloud storage vs gsutil

Both tools do the same job. The difference is age and direction of travel.

gcloud storagegsutil
StatusCurrent, actively developedLegacy, maintenance only
InstallationIncluded in the gcloud CLIStandalone Python-based tool
Syntaxgcloud storage cp …gsutil cp …
PerformanceFaster for large transfers and parallel operationsSlower for large files without extra configuration
AuthenticationShares gcloud credentials automaticallyRequires separate configuration
Use for new work?YesNo, unless maintaining existing scripts

Commands map directly between the two tools. gsutil cp becomes gcloud storage cp. gsutil ls becomes gcloud storage ls. Flag names occasionally differ: for example, gsutil cp -r versus gcloud storage cp —recursive.

You will see gsutil in a large proportion of existing tutorials and Stack Overflow answers. Knowing the equivalent gcloud storage command is straightforward once you understand the pattern.

How uploading files to Cloud Storage works

When you run a cp command, you specify a source path and a destination path. The destination is always a gs:// URL:

gcloud storage cp ./report.pdf gs://my-bucket/reports/report.pdf
#                 ^source       ^destination bucket   ^object name

Key things to understand before you start:

  • Object names are flat. Cloud Storage does not have real directories. The slash in reports/report.pdf is part of the object name, not a directory structure. It behaves like a folder in the console and most tools, but it is just a naming convention.

  • Overwriting is silent. If an object with the same name already exists, uploading replaces it without any warning or confirmation prompt. If you need to recover overwritten objects, enable versioning on the bucket before you need it.

  • Recursive uploads require a flag. Uploading a directory does not happen automatically. You must pass —recursive (gcloud) or -r (gsutil). Without this flag, the command will error or skip the directory.

  • Authentication is implicit. Commands use the credentials from gcloud auth login or a service account configured in the environment. If a command fails with a permission error, check your IAM permissions on the bucket.

  • Syncing vs copying. cp transfers files unconditionally. rsync compares source and destination and only transfers what has changed. For large deployments where most files are unchanged, rsync is considerably faster.

Common upload and download tasks

# Upload a single file
gcloud storage cp ./report.pdf gs://my-app-data/reports/report.pdf
# gsutil equivalent
gsutil cp ./report.pdf gs://my-app-data/reports/report.pdf

# Upload an entire local directory recursively
gcloud storage cp --recursive ./dist/ gs://my-app-assets/
# gsutil equivalent
gsutil cp -r ./dist/ gs://my-app-assets/

# Upload a large file using parallel composite uploads
gcloud storage cp --parallel-composite-upload-threshold=150M \
  ./large-dataset.tar.gz gs://my-app-data/

# Download a single file
gcloud storage cp gs://my-app-data/reports/report.pdf ./downloaded-report.pdf

# Download an entire prefix (simulated folder) from a bucket
gcloud storage cp --recursive gs://my-app-assets/ ./local-assets/

# List all objects in a bucket
gcloud storage ls gs://my-app-data/

# List with details (size and date)
gcloud storage ls -l gs://my-app-data/reports/

# List all objects recursively under a prefix
gcloud storage ls --recursive gs://my-app-data/reports/

# Copy an object within the same bucket (for renaming or reorganising)
gcloud storage cp \
  gs://my-app-data/old-name.txt \
  gs://my-app-data/new-name.txt

# Copy an object to a different bucket
gcloud storage cp \
  gs://source-bucket/data.json \
  gs://destination-bucket/archive/data.json

# Copy all objects under a prefix to another bucket
gcloud storage cp --recursive \
  gs://source-bucket/exports/ \
  gs://archive-bucket/2026/exports/
Move and rename

gcloud storage does not have a native mv command. To move an object, copy it then delete the original. gsutil mv exists and does this in one step, which remains a practical reason to reach for gsutil on occasion.

# Move an object using gcloud storage (two steps)
gcloud storage cp gs://my-bucket/old-path/file.csv gs://my-bucket/new-path/file.csv
gcloud storage rm gs://my-bucket/old-path/file.csv

# Move using gsutil (one step)
gsutil mv gs://my-bucket/old-path/file.csv gs://my-bucket/new-path/file.csv
Cross-region copies and cost

Copies within the same region are free of network egress charges. Cross-region copies incur egress fees. For large scheduled cross-region transfers, the Storage Transfer Service is better suited than CLI copies.

cp vs rsync: which one to use

Both commands move files to Cloud Storage but behave differently when the destination already contains files.

cprsync
TransfersAll specified files, every timeOnly new or changed files
Overwrites existing?Yes, silentlyOnly if source is newer or different
Deletes extras in destination?NoOnly if you pass the delete flag
Best forOne-off transfers, scripts needing certaintyRepeated deployments, incremental updates
Risk levelLowHigher when the delete flag is used
# Sync a local directory to a bucket (upload new and changed files only)
gcloud storage rsync ./dist/ gs://my-app-assets/

# Sync with deletion: also remove destination objects not present in the source
# Verify your source is complete before adding this flag
gcloud storage rsync --delete-unmatched-destination-objects \
  ./dist/ gs://my-app-assets/

# Sync two buckets
gcloud storage rsync gs://source-bucket/ gs://destination-bucket/

# gsutil equivalents
gsutil rsync -r ./dist/ gs://my-app-assets/
gsutil rsync -r -d ./dist/ gs://my-app-assets/  # -d deletes unmatched destination objects
Deletion flag

The —delete-unmatched-destination-objects flag (or -d in gsutil) permanently removes objects in the destination that are not in the source. On a bucket with versioning enabled, this creates delete markers rather than immediate permanent deletions. On a non-versioned bucket, objects are gone with no recovery path. Always confirm the source directory is complete before running rsync with deletion in production.

Deleting objects and buckets

Deletion in Cloud Storage is immediate and permanent on non-versioned buckets. Be specific with your paths.

# Delete a single object
gcloud storage rm gs://my-app-data/temp/scratch.log

# Delete all objects under a prefix
gcloud storage rm --recursive gs://my-app-data/temp/

# Delete a bucket and all its contents
gcloud storage rm --recursive gs://my-app-data/**
gcloud storage buckets delete gs://my-app-data

# gsutil: delete bucket and all contents in one command
gsutil rm -r gs://my-app-data

# Delete all versions of an object in a versioned bucket
gcloud storage rm --all-versions gs://my-app-data/config.json
Be specific with paths

gsutil rm -r gs://my-bucket/ deletes everything in the bucket and then the bucket itself. To remove only objects under a specific prefix, be explicit: gsutil rm -r gs://my-bucket/temp/. A missing trailing slash can have unintended consequences. If you are unsure what will be deleted, run gcloud storage ls —recursive on the path first.

When to use CLI uploads

CLI uploads are the right choice when you have GCP credentials in your environment and need to move files directly. Common scenarios:

  • Deploying a static website. Build your site locally or in CI, then run rsync to push the output to a Cloud Storage bucket configured for static hosting. Only changed files are transferred on subsequent deploys. The storage class you choose affects how you are billed for access to those assets.

  • Uploading reports or data exports. A script that generates a report or data export can use cp to push the output to a known bucket path immediately after generation.

  • Archiving and backing up files. Copy files from a server or local machine to Cloud Storage as part of a backup routine. Pair with object lifecycle management to automatically move old backups to cheaper storage classes over time.

  • Moving data between buckets. Use cp —recursive to copy a prefix from one bucket to another, for example when migrating data to a new region or separating environments.

  • CI/CD pipelines. After a successful build, upload artefacts to Cloud Storage for distribution or downstream consumption. This is a common pattern across GCP deployment workflows.

If you need external users or services to upload directly without GCP credentials, CLI uploads are the wrong tool. Use signed URLs instead. Signed URLs grant temporary, scoped access to a specific object without requiring any GCP account.

Setting metadata and content type

Cloud Storage uses object metadata to tell browsers and CDNs how to handle served files. If you are hosting a static website or serving assets through a CDN, getting these right matters.

Without an explicit Content-Type, served files may be treated as application/octet-stream. Browsers will download them rather than render them. Cloud Storage attempts to infer content type from the file extension, but inference is not always correct, particularly for less common file types.

# Upload with an explicit content type
gcloud storage cp ./index.html gs://my-static-site/ \
  --content-type=text/html

# Upload with a custom cache-control header (gsutil)
gsutil -h "Cache-Control:public,max-age=3600" \
  cp ./logo.png gs://my-static-site/images/logo.png

# Update metadata on an existing object (gsutil)
gsutil setmeta -h "Content-Type:application/json" \
  gs://my-app-data/config.json

# View object metadata
gcloud storage objects describe gs://my-app-data/config.json
Static site caching strategy

Set Cache-Control: no-cache on HTML files so browsers always re-fetch the latest version. Set a long max-age (such as one year) on versioned asset files with hashed names (JavaScript, CSS, images). This way you get fast cached assets for returning visitors and immediate updates when content changes.

Access controls for serving assets publicly are covered in the Cloud Storage IAM vs ACLs guide, and broader data protection in the Cloud Storage Security guide.

Common beginner mistakes

  1. Forgetting the recursive flag when uploading a directory. Running gcloud storage cp ./dist/ gs://my-bucket/ without —recursive will either error or skip the directory entirely. Always add —recursive (gcloud) or -r (gsutil) when copying a directory.

  2. Getting the bucket path wrong. A destination of gs://my-bucket and gs://my-bucket/ can behave differently depending on context. When uploading to a specific prefix, be explicit: gs://my-bucket/prefix/filename.txt. Vague destination paths can land files in unexpected locations.

  3. Running rsync with deletion before confirming the source is complete. If the source directory is partial (interrupted build, missing mount point, incomplete sync), rsync with the delete flag will permanently remove destination objects you intended to keep. List both sides and compare before running rsync with deletion in production.

  4. Uploading web assets without setting correct content types. Cloud Storage infers content type from the file extension, but inference is not always correct. Files served without an explicit Content-Type may be treated as application/octet-stream, causing browsers to download them rather than render them. Set content types explicitly for HTML, CSS, JavaScript, and image files in static website buckets.

  5. Uploading large files without parallel composite uploads. A multi-GB file uploaded over a single TCP stream is slow and fails if the connection drops. For files over 100 MB, use —parallel-composite-upload-threshold in gcloud storage to split the upload into parallel parts. Resumable uploads are the default in client libraries and gsutil for files above 8 MB and restart from where they left off if the connection drops.

  6. Assuming overwrite is safe without versioning enabled. Uploading to a path that already exists replaces the object silently. There is no confirmation. Enable versioning on buckets where accidental overwrites would be a problem. Do this before you need it, not after.

Frequently asked questions

Should I use gcloud storage or gsutil?

Use gcloud storage for all new scripts and workflows. It is the modern replacement for gsutil, built into the gcloud CLI, with better performance for large transfers and better integration with the rest of gcloud. gsutil is a legacy Python-based tool that still works but is no longer being actively developed. You will see gsutil throughout older documentation and Stack Overflow answers, so it is worth knowing both, but write new automation with gcloud storage.

How do I upload an entire folder to Cloud Storage?

Use the --recursive flag with gcloud storage cp: gcloud storage cp --recursive ./my-folder/ gs://my-bucket/prefix/. This uploads all files inside the folder and preserves the directory structure as object name prefixes. The equivalent gsutil command is gsutil cp -r ./my-folder/ gs://my-bucket/prefix/.

What is the difference between cp and rsync?

cp copies files unconditionally from source to destination, including files that already exist at the destination. rsync synchronises the destination to match the source: it copies new files, updates changed files, and can optionally delete destination files that no longer exist in the source. rsync is more efficient for incremental updates because it skips files that are already up to date. Use cp for one-off transfers and rsync for ongoing or repeated deployments.

Does uploading a file overwrite an existing object with the same name?

Yes. If an object with the same name already exists in the bucket, uploading a new file to the same path replaces it silently. If you have versioning enabled on the bucket, the previous version is preserved as a non-current version rather than deleted. Without versioning, the old object is gone immediately. Enable versioning on buckets where accidental overwrites would be a problem.

When should I use signed URLs instead of CLI uploads?

Use CLI uploads when you control the environment and have GCP credentials, such as from a CI/CD pipeline, a server, or your own workstation. Use signed URLs when you need to let an external user or service upload or download directly to Cloud Storage without GCP credentials. Signed URLs grant temporary, scoped access to a specific object and are the right approach for user-facing uploads from a browser or a third-party service.

Last verified: 23 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.