Cloud Storage for ML#

Create and use a Cloud Storage bucket

A model is not a single file. It is a chain of data, code, weights, metrics, and predictions. Cloud storage gives that chain a home that is not your laptop.

⏱ ~10 min read Β· ~25 min hands-on πŸ”— needs: Config Management Β· Cost Alerting & Budgets

The mental model#

Google Cloud Storage (GCS) is object storage. A bucket is a globally named container; an object is a file inside it. gs:// is GCS’s equivalent of a filesystem path:

gs://tds-your-name-2026/
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ raw/          # immutable source files
β”‚   └── processed/    # reproducible derived files
β”œβ”€β”€ models/           # trained weights / model packages
β”œβ”€β”€ runs/             # charts, predictions, evaluation reports
└── README.md         # what lives here, who owns it, when to delete it

Folders above are a convenient name prefix, not real directories. GCS stores the object named data/raw/train.csv; it does not create a folder first.

Store it inGood forNot good for
Gitcode, small configs, model cardslarge data, weights, secrets
GCSdatasets, images, model artifacts, batch outputsquerying rows with SQL
BigQuerystructured, queryable tables and featuresarbitrary model files
a databaseapp state and frequent small reads/writesa multi-GB training dataset

Rule of thumb: Git records how to make an artifact; GCS stores the artifact itself. Put the GCS URI, data version, and preprocessing code in Git.

Why ML projects need it#

Local paths such as /Users/me/Downloads/final_final.csv make a project impossible to reproduce. A shared object URI lets a notebook, a training job, and a deployed service use the same input or model artifact.

GCS also fits the common ML hand-off:

collect data β†’ gs://.../data/raw/ β†’ clean/train β†’ gs://.../models/ β†’ serve or batch-score

It does not make data correct or safe. A bucket happily preserves the wrong CSV, an accidentally public file, or a model with no explanation. Naming, access control, and lifecycle rules are your job.

Try it β€” make a small ML artifact store#

1. Set a project and create a bucket#

Install and sign in to the Google Cloud CLI, then choose a GCP project that has billing enabled. Bucket names are global across GCS, so include a unique suffix.

export PROJECT_ID="your-gcp-project-id"
export BUCKET="tds-${USER,,}-ml-2026-unique-suffix"

gcloud config set project "$PROJECT_ID"
gcloud storage buckets create "gs://$BUCKET" \
  --location=asia-south1 \
  --uniform-bucket-level-access

Use a region near your compute. Keeping a training job and its bucket in the same region reduces latency and can avoid unnecessary network charges. --uniform-bucket-level-access means access is granted with IAM roles on the bucket, rather than ad-hoc permissions on individual files.

⚠️ Never train on or upload personal/sensitive data to a course bucket without permission. Do not make a bucket public just to fix an AccessDenied error. Grant the smallest needed IAM role to the person or service account that needs it.

2. Upload a tiny, versioned dataset#

Make two small files locally. In a real project these may be images, Parquet files, JSONL prompts, or a model checkpoint.

mkdir -p data/raw data/processed models
printf 'hours_studied,passed\n2,0\n6,1\n' > data/raw/train.csv
printf 'hours_studied,passed\n4,1\n' > data/raw/test.csv

# Keep a release/date in the path. Do not overwrite raw data silently.
gcloud storage cp --recursive data/raw "gs://$BUCKET/data/raw/v1/"
gcloud storage ls --recursive "gs://$BUCKET/data/"

You should see two gs://.../data/raw/v1/... objects. Downloading is symmetric:

gcloud storage cp "gs://$BUCKET/data/raw/v1/train.csv" ./data/raw/train-copy.csv

3. Upload the model and its evidence#

An artifact without context is usually useless six weeks later. Store a model alongside a short metrics file and the exact data URI that produced it.

printf 'pretend model bytes\n' > models/baseline-v1.joblib
printf '%s\n' \
  "data: gs://$BUCKET/data/raw/v1/" \
  'metric: accuracy=0.83' \
  'code_commit: paste-a-git-commit-here' > models/baseline-v1.metrics.txt

gcloud storage cp models/baseline-v1.joblib "gs://$BUCKET/models/baseline/v1/"
gcloud storage cp models/baseline-v1.metrics.txt "gs://$BUCKET/models/baseline/v1/"

Later, MLflow can record this metadata automatically; the principle stays the same.

Read and write from Python#

The CLI is excellent for setup and debugging. Application code usually uses a client library and authenticates as its runtime service account, not with a downloaded key file.

uv add google-cloud-storage
from google.cloud import storage

bucket_name = "tds-your-name-ml-2026-unique-suffix"
source_path = "models/baseline-v1.joblib"
object_name = "models/baseline/v1/baseline-v1.joblib"

client = storage.Client()
bucket = client.bucket(bucket_name)

# Upload
blob = bucket.blob(object_name)
blob.upload_from_filename(source_path)
print(f"uploaded: gs://{bucket_name}/{object_name}")

# Download to a different local path
blob.download_to_filename("models/downloaded-baseline-v1.joblib")

Run this after gcloud auth application-default login on your own computer. In Cloud Run, Vertex AI, or another GCP service, use a service account with a narrowly scoped Storage IAM role instead of local user credentials.

Make storage reproducible, safe, and cheap#

NeedPractical habit
Reproduce a training runWrite immutable data under data/raw/v1/, v2/, … and record the full gs:// URI in the run metadata.
Recover from an overwrite/deleteEnable Object Versioning on important buckets. It protects you, but old versions still cost money.
Share a modelGrant Storage Object Viewer to a specific service account or groupβ€”not allUsers.
Avoid surprise billsAdd a lifecycle rule that deletes temporary exports/checkpoints; set a project budget first.
Keep data understandablePut a README.md or manifest.json beside each dataset version: source, schema, licence, date, owner, and PII status.

Avoid overwriting data/raw/latest.csv. A latest pointer is fine for convenience, but the experiment must record an immutable version such as data/raw/2026-08-26/ or an object generation number.

When it fails#

SymptomLikely causeFix
AccessDeniedExceptionYour user/service account lacks a Storage IAM roleCheck the active account with gcloud auth list; grant the minimum bucket-level role. Do not make the bucket public.
Bucket creation says the name existsBucket names are globalChange the suffix; use lowercase names.
Training cannot find a fileLocal path was used instead of a gs:// URI, or the prefix is wrongList the exact object with gcloud storage ls --recursive gs://BUCKET/PREFIX.
A run cannot be reproducedSource data was overwritten or untrackedUse versioned prefixes and record URI, code commit, and preprocessing version.
Bill is larger than expectedOld checkpoints/versions, cross-region transfer, or abandoned dataInspect prefixes, add lifecycle rules, and keep storage and compute in one region.

Your turn (β‰ˆ25 min)#

  1. Create a bucket with uniform bucket-level access.
  2. Upload one raw dataset under a versioned path and list it from the CLI.
  3. Upload a model artifact plus a one-file manifest containing its input URI and one metric.
  4. Download the artifact to a different path and compare its checksum with the original (sha256sum).
  5. Before leaving the project, set a lifecycle rule or delete the test bucket and its objects. Do not leave unnamed course experiments accumulating.

Checklist#

  • I can explain a bucket, object, and gs:// URI.
  • Raw data, derived data, and models have different prefixes.
  • My training run records an immutable data URI and code version.
  • My bucket is private and uses IAM, not public object URLs.
  • I know what will delete temporary files and when.

Go deeper#