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.cpcopies a file from one location to another. The source and destination can be local paths orgs://bucket paths.rsynckeeps 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 storageis the modern CLI for Cloud Storage, built into the gcloud CLI you already use for other GCP services.gsutilis 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 storage | gsutil | |
|---|---|---|
| Status | Current, actively developed | Legacy, maintenance only |
| Installation | Included in the gcloud CLI | Standalone Python-based tool |
| Syntax | gcloud storage cp … | gsutil cp … |
| Performance | Faster for large transfers and parallel operations | Slower for large files without extra configuration |
| Authentication | Shares gcloud credentials automatically | Requires separate configuration |
| Use for new work? | Yes | No, 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 nameKey things to understand before you start:
Object names are flat. Cloud Storage does not have real directories. The slash in
reports/report.pdfis 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 loginor 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.
cptransfers files unconditionally.rsynccompares 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/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.csvCopies 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.
| cp | rsync | |
|---|---|---|
| Transfers | All specified files, every time | Only new or changed files |
| Overwrites existing? | Yes, silently | Only if source is newer or different |
| Deletes extras in destination? | No | Only if you pass the delete flag |
| Best for | One-off transfers, scripts needing certainty | Repeated deployments, incremental updates |
| Risk level | Low | Higher when the delete flag is used |
Restocking a shop vs topping it up
cp is like a delivery driver who brings the full stock list every morning
regardless of what is already on the shelves. rsync is like a driver who checks
what ran out and only replaces those items. For a small shop that changes daily it barely
matters. For a large warehouse where 95% of the stock never moves, rsync saves a lot of
unnecessary trips.
# 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 objectsThe —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.jsongsutil 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
rsyncto 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
cpto 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 —recursiveto 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.jsonSet 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.
A command-line post office
gcloud storage cp is the equivalent of posting a parcel or collecting one.
gcloud storage ls is checking the post box to see what has arrived.
gcloud storage rsync is telling the post office to keep your local filing cabinet
synchronised with the one at head office: only send changes, do not re-send everything every
day. gcloud storage rm is asking the post office to shred a specific parcel
permanently.
Common beginner mistakes
Forgetting the recursive flag when uploading a directory. Running
gcloud storage cp ./dist/ gs://my-bucket/without—recursivewill either error or skip the directory entirely. Always add—recursive(gcloud) or-r(gsutil) when copying a directory.Getting the bucket path wrong. A destination of
gs://my-bucketandgs://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.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.
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-Typemay be treated asapplication/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.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-thresholdin 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.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.
Summary
- Use
gcloud storagefor all new scripts; usegsutilwhen maintaining existing code or when you needmv - Core commands:
cp(copy),ls(list),rm(delete),rsync(sync) - Use
—recursive(gcloud) or-r(gsutil) for directories and prefixes - Use
cpfor one-off transfers; usersyncfor incremental updates and repeated deployments - The delete flag in rsync permanently removes unmatched destination objects; confirm the source is complete first
- Uploading replaces existing objects silently; enable versioning if you need recovery
- Set content types explicitly for web assets; use parallel uploads for large files
- Copies within the same region are free of egress charges; cross-region copies incur network fees
- For external user uploads without GCP credentials, use signed URLs instead of CLI uploads
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.