Designing Scalable Data Pipelines for Earth Observation in the Cloud
Updated on March 31, 2026 19 minutes read
Earth observation data is no longer a niche scientific asset that lives in isolated archives and gets processed one scene at a time. Platforms such as NASA Earthdata, Copernicus, and the long-running Landsat program have made large-scale satellite data central to climate science, agriculture, hydrology, disaster response, and environmental policy.
That shift changes the engineering problem. The challenge is not downloading a single raster and opening it in a desktop GIS tool. The challenge is designing cloud-native systems that can ingest multi-terabyte archives, preserve metadata and lineage, create analysis-ready representations, and feed reliable ML workflows without becoming brittle or prohibitively expensive.
This article is for data engineers, ML engineers, geospatial developers, and environmental researchers who already know some Python or cloud fundamentals and want a deeper architectural view. It is also useful for career-switchers who want to understand how software engineering, cloud computing, and environmental science intersect in real production systems.
Why Earth observation pipelines need cloud-native design
A simple remote-sensing workflow often starts with a folder of scenes, some preprocessing scripts, and a final export. That works for a local study or a classroom project, but it does not scale well when you need years of observations, multiple sensors, repeated retraining, or reproducible outputs across many regions.
A cloud-native design treats Earth observation as a data platform problem. Raw scenes are stored in object storage, metadata is indexed for search and filtering, derived arrays are chunked for repeated analysis, and ML-ready tables are materialized for fast training and scoring. That design is not about trend-following. It is about matching the storage format to the way the data will actually be read.
This matters especially in environmental domains where the real signal is temporal. Crop stress, flood expansion, vegetation decline, surface water variability, and land-cover change are not just image-classification problems. They are spatiotemporal monitoring problems, and that means the pipeline architecture has a direct effect on scientific validity and operational usefulness.
Background and prerequisites
A reader will get the most out of this article with some basic Python, NumPy, or pandas, and familiarity with cloud storage concepts such as buckets and object paths. It also helps to know the difference between raster and vector data, and to be comfortable with standard ML ideas such as train-test splits, classification metrics, and feature engineering.
On the domain side, it is useful to understand what a satellite band represents, why reprojection matters, and why quality masks matter. Environmental problems often depend on seasonality, acquisition timing, cloud cover, and sensor differences, so pipeline design must preserve those details rather than flatten them away.
Earth observation data is inherently spatiotemporal. A single image is a measurement of the Earth's surface at a specific time and over a specific footprint. In agriculture and climate work, the most useful information often comes from time series rather than isolated scenes: vegetation cycles, drought anomalies, water stress, flood recurrence, and seasonal deviations are all patterns that unfold across time.
The technical stack has also matured around a small set of interoperable standards. STAC gives a standard way to describe spatiotemporal assets. Cloud Optimized GeoTIFF enables efficient partial reads from raster files in object storage. Parquet is a columnar format for feature tables and metadata. GeoParquet extends Parquet for vector geometries. Zarr stores chunked N-dimensional arrays and is especially well suited to cloud-based analysis.
In Python, xarray and Dask are a natural pairing for this work. Xarray lets you work with named dimensions and coordinates, while Dask makes it possible to process arrays that are larger than memory by evaluating operations lazily over chunks.
Thinking about Earth observation as a data cube plus an index
A useful abstraction for Earth observation is a labeled data cube:
Here, is time, is band or channel, and and are spatial dimensions. This representation is useful because it matches how scientists think about environmental monitoring: repeated measurements of the same places over time across multiple spectral signals.
Alongside the cube, you need metadata:
The important architectural lesson is that the metadata layer and the pixel layer have different access patterns. A query such as “find all scenes intersecting this watershed in July with cloud cover below 20%” is not the same as “extract 12 months of NIR and red bands for 50,000 training patches.” Forcing both workloads into a single representation usually creates performance and maintenance problems.
That is why scalable systems are usually separateinto four layers. The first layer holds raw or standardized scene assets. The second layer handles discovery and indexing, often through STAC and tabular search structures. The third layer stores analysis-ready arrays for repeated slicing. The fourth layer stores ML-ready features, labels, and model artifacts.
This separation is not unnecessary complexity. It is what makes the system debuggable and scalable. A data scientist can iterate on features without touching raw ingest logic, and a data engineer can improve indexing or partitioning without changing the scientific meaning of the labels.
Why tiling and chunking dominate performance
Cloud object storage is optimized for durability and scale, but performance depends heavily on request patterns. A useful high-level model for read time is:
Here, is the number of network requests, is average latency per request, is the number of bytes read, is effective bandwidth, and is the time needed to decompress and parse the data.
This explains why chunking is not a minor implementation detail. If chunks are too small, the system makes too many requests and spends much of its time on overhead. If chunks are too large, every small training patch or parcel read pulls in a large amount of irrelevant data.
A spatial tiling scheme can be written simply as:
That tells you how a coordinate maps to a tile grid, but it does not tell you what the best tile or chunk size should be. In practice, chunking must be optimized against real workloads. A more honest objective is:
Here, is a query type, is the weight or frequency of that query type, and is the chunk configuration. In other words, the “best” chunk layout is the one that minimizes the weighted cost of the queries you actually run most often.
In Earth observation systems, this trade-off is central. A chunk layout optimized for full-scene browsing may be poor for parcel-level training data extraction. A layout optimized for monthly patch extraction may be poor for regional mosaics. Designing around expected workloads is therefore one of the most important architectural decisions in the entire pipeline.
Choosing between STAC, COG, Parquet, GeoParquet, Zarr, and array databases
These tools are often presented as alternatives, but in practice, they are complementary. STAC solves the discovery and metadata problem. It gives catalogs, collections, and items a common vocabulary so that different systems can query and exchange geospatial assets more consistently.
COG solves a scene-level cloud-access problem. Because a COG is internally tiled and organized for HTTP byte-range access, clients can request only the parts of a raster they need rather than downloading the whole file. This makes it excellent for raw scenes, map serving, and interoperable distribution.
Parquet and GeoParquet solve the tabular analytics problem. Feature tables, labels, scene indexes, and vector boundaries are often read selectively by column and partition. Columnar formats are a strong fit for this kind of workload, especially once pipelines move beyond experiments into repeated training and scoring.
Zarr solves the multidimensional array problem. It stores chunked arrays explicitly and works naturally with xarray and Dask, which makes it attractive for data cubes, temporal composites, and repeated spatiotemporal slicing. When the main workload is “read a specific set of bands across time for many patches,” Zarr is often much more efficient than trying to reconstruct the same access pattern from scene files again and again.
Array databases such as TileDB can go one step further by offering a stronger storage and query model over dense or sparse arrays. They are especially useful when you want a unified multidimensional abstraction across raster cubes, sparse observations, and derived annotations.
A practical rule of thumb is simple. Use STAC for discovery and provenance. Use COG for raw scenes and interoperable raster exchange. Use Parquet or GeoParquet for metadata, labels, and feature tables. Use Zarr or an array database when repeated multidimensional analysis is a first-class workload.
Why ML-ready datasets are a separate product
One of the most common mistakes in geospatial ML is assuming that imagery plus labels automatically equals a training dataset. In reality, an ML-ready Earth observation dataset is a carefully versioned product that maps raw observations into training examples with explicit decisions about time windows, masking, spatial boundaries, and label definitions.
Suppose you are building a binary classifier for crop stress. A parcel-level label might be derived from field surveys or agronomic assessments, but the source signal lives in a time series of pixels. You still need to decide whether a training example is a parcel, a patch, a tile, a monthly summary, or a sequence. Each decision changes the learning problem.
For binary classification, a common objective is weighted cross-entropy:
The weight matters because many environmental tasks are imbalanced. Flood events are rarer than non-flood conditions. Stressed fields are rarer than healthy fields. Illegal land-use cases are rarer than ordinary land cover. The weighting is not just a statistical adjustment. It encodes an operational priority, such as reducing the cost of missing truly important cases.
Temporal aggregation is another major design choice. A monthly composite can be written as:
This kind of aggregation often improves robustness by reducing scene-to-scene noise and residual cloud contamination while preserving the seasonal structure that many environmental tasks depend on.
Hands-on implementation: from a Zarr cube to an ML-ready Parquet feature store

A realistic architecture often stores raw scenes as COGs referenced by STAC, then materializes analysis-ready seasonal or monthly cubes in Zarr. Labels and downstream features are then stored in Parquet or GeoParquet, which makes the training loop much lighter and easier to version.
A storage layout might look like this:
s3://eo-lake/
raw/
collection=sentinel-2/year=2025/month=06/...
catalog/
stac/...
silver/
cubes/region=indus_basin/season=2025/*.zarr
gold/
labels/season=2025/*.parquet
features/season=2025/tile_id=.../*.parquet
predictions/model=crop_stress/version=v1/...
In the example below, the upstream ingest pipeline has already discovered scenes, standardized them to a common grid, and written monthly composites to a Zarr store. We will now build parcel or patch-level features and train a baseline classifier for crop stress.
import xarray as xr
import pandas as pd
import numpy as np
import pyarrow as pa
import pyarrow.dataset as ds
from sklearn.model_selection import GroupShuffleSplit
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import f1_score, average_precision_score, classification_report
from sklearn.utils.class_weight import compute_sample_weight
# Open a Dask-backed Zarr cube from object storage.
# Dimensions might look like: time, y, x
# Variables might include reflectance bands such as B02, B03, B04, B08
cube = xr.open_zarr(
"s3://eo-lake/silver/monthly_cubes/region=indus_basin/season=2025/",
consolidated=True
)
# Read parcel or patch labels.
# In a geometry-aware workflow this could be a GeoParquet table upstream,
# but here we assume the relevant bounding coordinates are already present.
labels = pd.read_parquet("s3://eo-lake/gold/labels/season=2025/")
required_cols = {"patch_id", "tile_id", "x0", "y0", "x1", "y1", "label"}
missing = required_cols - set(labels.columns)
if missing:
raise ValueError(f"Missing required columns: {missing}")
# Add common remote-sensing indices used in agriculture and vegetation analysis.
cube["ndvi"] = (cube["B08"] - cube["B04"]) / (cube["B08"] + cube["B04"] + 1e-6)
cube["ndwi"] = (cube["B03"] - cube["B08"]) / (cube["B03"] + cube["B08"] + 1e-6)
# Convert repeated acquisitions into monthly composites.
# Median is a robust choice when you want to dampen outliers and residual artifacts.
monthly = cube.groupby("time.month").median(dim="time")
The key point is that nothing here requires loading the full archive into memory. Xarray gives you named dimensions and coordinates, while the Zarr-backed array remains lazy until you explicitly compute summaries. That is exactly what you want when the archive is much larger than a single machine.
The vegetation index used in the code is defined as:
This index is useful because stressed vegetation often departs from normal seasonal behavior. In agriculture, the signal is not just “how green is this parcel today,” but “how does this parcel behave over the season compared with expected growth patterns.”
That shift from single observations to temporal behavior is what makes NDVI especially valuable in real-world monitoring systems.
def summarize_patch(monthly_ds, patch_row):
"""
Extract summary statistics for one patch.
We assume x coordinates ascend left-to-right and y coordinates descend top-to-bottom.
"""
patch = monthly_ds.sel(
x=slice(patch_row.x0, patch_row.x1),
y=slice(patch_row.y1, patch_row.y0)
)
if patch.sizes.get("x", 0) == 0 or patch.sizes.get("y", 0) == 0:
return None
row = {
"patch_id": patch_row.patch_id,
"tile_id": patch_row.tile_id,
"label": int(patch_row.label),
}
for var in ["B02", "B03", "B04", "B08", "ndvi", "ndwi"]:
# Average pixels inside the patch, keeping the month axis.
ts = patch[var].mean(dim=("x", "y")).compute()
row[f"{var}_season_mean"] = float(ts.mean().item())
row[f"{var}_season_std"] = float(ts.std().item())
row[f"{var}_peak"] = float(ts.max().item())
row[f"{var}_trough"] = float(ts.min().item())
row[f"{var}_amplitude"] = row[f"{var}_peak"] - row[f"{var}_trough"]
# Preserve month-level shape information for the classifier.
for month in ts["month"].values:
row[f"{var}_m{int(month):02d}"] = float(ts.sel(month=int(month)).item())
return row
feature_rows = []
for patch_row in labels.itertuples(index=False):
result = summarize_patch(monthly, patch_row)
if result is None:
feature_rows.append(result)
feature_df = pd.DataFrame(feature_rows)
print(feature_df.head())
This step turns a spatiotemporal cube into a table of learning examples. That may feel like a loss of fidelity, but it is often the right first move in production settings because it creates a compact, inspectable feature store that is easy to version, validate, and join with other structured data.
If you want reproducibility, store the output as partitioned Parquet. Partitioning by tile, season, or region makes downstream reads cheaper, and Parquet is a natural fit because you usually score only a subset of columns or partitions at a time. If you need to keep parcel geometry attached, GeoParquet is the right option for that table.
#Persist features as partitioned Parquet.
table = pa.Table.from_pandas(feature_df, preserve_index=False)
ds.write_dataset(
table,
base_dir="s3://eo-lake/gold/features/season=2025/",
format="parquet",
partitioning=["tile_id"],
existing_data_behavior="delete_matching"
)
The final stage is model training. For Earth observation tasks, evaluation design is as important as the model itself. Nearby patches are often highly correlated, so a naive random split will overestimate performance by leaking local geography into both train and test sets.
X = feature_df.drop(columns=["patch_id", "tile_id", "label"])
y = feature_df["label"]
groups = feature_df["tile_id"]
# Group-based split to avoid spatial leakage across neighboring patches.
splitter = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
train_idx, test_idx = next(splitter.split(X, y, groups=groups))
X_train, X_test = X.iloc[train_idx], X.iloc[test_idx]
y_train, y_test = y.iloc[train_idx], y.iloc[test_idx]
sample_weight = compute_sample_weight(class_weight="balanced", y=y_train)
model = Pipeline(
steps=[
("imputer", SimpleImputer(strategy="median")),
("clf", HistGradientBoostingClassifier(
learning_rate=0.05,
max_depth=6,
max_iter=300,
random_state=42
)),
]
)
model.fit(X_train, y_train, clf__sample_weight=sample_weight)
# Probability outputs are useful for threshold selection and operational triage.
y_score = model.predict_proba(X_test)[:, 1]
# Lower threshold to favor recall in a rare-event monitoring workflow.
threshold = 0.35
y_pred = (y_score >= threshold).astype(int)
print("F1:", round(f1_score(y_test, y_pred), 3))
print("PR-AUC:", round(average_precision_score(y_test, y_score), 3))
print(classification_report(y_test, y_pred, digits=3))
This is a strong baseline because it is fast, handles missingness reasonably well, and produces probability scores you can calibrate later. In operational environmental work, threshold selection often reflects workflow constraints rather than pure model aesthetics. If a field team can inspect only the top 5% of highest-risk parcels, recall at a fixed review budget may matter more than raw accuracy.
Production and operations: what changes at scale

Most Earth observation pipelines are batch or micro-batch systems rather than true streams. Scenes arrive on a schedule, get validated, indexed, standardized, and folded into derived products such as monthly composites or rolling seasonal cubes. In emergency response systems, low-latency processing can matter more, but for many climate and agriculture workloads, correctness and reproducibility are more important than milliseconds.
In production, the biggest mistake is trying to make one storage representation serve every workload. A scene-friendly format is not always a feature-friendly format. A feature-friendly format is not always ideal for scientific array slicing. Keeping multiple layers may use more storage, but it usually reduces compute waste and engineering complexity.
A common production pattern starts with immutable raw assets and checksums in object storage. Ingest jobs validate files and update metadata catalogs. Standardization jobs handle reprojection, QA masking, harmonization, and chunk layout. Derived data products, such as regional seasonal cubes and feature tables, are then materialized in separate layers.
The cloud trade-offs are practical rather than abstract. COG is strong for scene access and distribution. Zarr is strong for repeated multidimensional reads. Parquet is strong for feature scans and metadata joins. GPUs are often unnecessary for ingestion and feature engineering, which are usually dominated by I/O and CPU-bound transforms. They become more relevant later if you move to deep learning models or very large training workloads.
Cost control depends heavily on avoiding repeated remote reads and poorly aligned chunking. Recomputing the same temporal composites for every experiment can become very expensive. Materializing stable intermediate datasets often costs less in the long run and also makes experiments much easier to compare.
Observability is another area where geospatial systems need domain-specific thinking. It is not enough to know whether jobs succeeded. Useful metrics include ingest lag, chunk read amplification, missing time steps by region, cloud-mask failure rates, label freshness, and calibration drift across land-cover classes or geographic zones.
Risk, ethics, safety, and governance
Environmental data pipelines are often presented as neutral because the source imagery is a physical measurement. In practice, they are not neutral at all. Bias enters through sensor coverage, cloud contamination, label collection practices, accessible sampling locations, and domain assumptions baked into preprocessing and target definition.
A crop-stress model trained mostly on irrigated, well-documented regions may fail in rain-fed systems or underrepresented climates. A flood classifier may look accurate overall while missing the exact types of terrain where missed detections matter most. These are not only model risks. They are data pipeline risks because the training set is a product of the pipeline.
Privacy also becomes relevant sooner than many geospatial teams expect. Satellite imagery itself may be open, but when it is linked with parcel records, ownership data, inspections, subsidy records, or household-level operational systems, the result may fall under data protection requirements. The GDPR matters whenever processing involves personal data.
A sensible default architecture separates open imagery from restricted operational joins. Access controls should be least-privilege, credentials should be short-lived, and sensitive labels or feature tables should be encrypted and audited. Governance is easiest to enforce when these boundaries are built into the storage design rather than patched on later.
Reliability is another critical issue. Earth observation models fail in familiar ways: cloud masks can be imperfect, georegistration can drift, labels can be stale, and mixed pixels can blur parcel boundaries. A system that does not surface uncertainty and known failure modes can be misleading even when the aggregate metrics look reasonable.
The NIST AI Risk Management Framework is useful here because it emphasizes governance, risk mapping, measurement, and management. In an Earth observation context, that means documenting intended use, keeping lineage from source scenes to model outputs, measuring failure modes by region or class, and ensuring that high-stakes actions are reviewed by humans rather than triggered solely by model scores.
Case study: crop-stress monitoring for irrigation planning
Imagine a regional agriculture team responsible for prioritizing field visits during a dry season. The team has access to open satellite imagery, parcel boundaries, some weather summaries, and a limited set of field observations. They want a system that can highlight which areas most likely require attention.
This is a realistic Earth observation problem because the data volume is large, the monitoring question repeats across time, and the output supports operational decisions rather than one-off analysis. It also illustrates the interdisciplinary nature of the work: cloud architecture and ML matter, but so do agronomy, seasonality, irrigation practices, and the consequences of false negatives.
A good first version of the system would ingest scenes into object storage, update a STAC catalog, standardize imagery onto a consistent grid, build monthly composites in Zarr, and generate parcel-level features in Parquet. A baseline model would score parcels for likely stress while preserving provenance and quality flags.
The model itself might be relatively simple. A gradient-boosted classifier can work well with engineered time-series summaries and is much easier to explain to field teams than a more opaque deep architecture. In many public-interest workflows, a transparent baseline that is operationally reliable is more valuable than a slightly stronger model that is hard to explain.
The output should not be used as a final decision. A stress score is best understood as a prioritization signal. High-scoring parcels can be reviewed by agronomists, compared with local weather or irrigation data, and used to guide field visits or advisory efforts. That human-in-the-loop design is what turns a predictive system into a trustworthy operational tool.
Skills mapping and a practical learning path
For learners, this topic develops multiple skill sets at once. The programming side includes Python, xarray, pandas, PyArrow, and cloud storage workflows. Those are broadly useful skills even outside geospatial work.
The systems side includes data layout design, partitioning, chunking, orchestration, reproducibility, observability, and cost-aware cloud architecture. These are the skills that separate a notebook experiment from a production-ready data product.
The domain side includes remote-sensing fundamentals, environmental signal interpretation, temporal reasoning, and awareness of domain-specific risks such as representativeness, uncertainty, and operational misuse. That combination is what makes Earth observation such a strong learning area for interdisciplinary engineers.
A sensible progression is to start with one sensor, one region, and one season. Build a tiny STAC-backed scene index, materialize a small Zarr cube, create a feature table in Parquet, and compare random validation against grouped spatial validation. That sequence teaches both software and a scientific discipline.
After that, the next step is to add complexity slowly. Introduce more sensors, compare alternative chunk layouts, add weather or vector data, measure cost, and track model performance by geography rather than only by global averages. That is where real production thinking begins.
Conclusion
Scalable Earth observation pipelines work best when raw scenes, metadata catalogs, analysis arrays, and ML-ready features are treated as separate but connected products. That architectural separation is what makes the system maintainable, queryable, and adaptable to different workloads.
The core technical challenge is not just storage size. It is designed for the right access patterns, preserving lineage, reducing recomputation, and evaluating models honestly in the presence of spatial and temporal correlation. Those are engineering questions, but they are also scientific questions because they affect what the model is actually learning.
The domain impact is just as important. In agriculture, hydrology, climate adaptation, and environmental policy, better pipelines support better decisions. That does not mean automation replaces expertise. It means well-designed systems can help experts focus their time, review stronger signals, and act on more reliable information.
The best way to learn this material is to build a small but complete version yourself. Start with a narrow use case, design the data layers carefully, and only then scale outward. In Earth observation, good architecture is not an afterthought. It is part of the scientific method.
Ready to build these skills with Code Labs Academy?
If this article made you want to move from theory into practice, explore the Data Science & AI Bootcamp at Code Labs Academy. It is a strong next step for learners who want to build Python, machine learning, and production data skills in a structured environment.
For a broader view of the available paths, visit the full Courses page. That is useful if your interests span data, software engineering, cybersecurity, or web development, and you want to understand how these skills connect.
A strong companion read is MLOps Roadmap for Beginners (2026). It complements this article well because scalable Earth observation systems depend as much on reproducibility, monitoring, and deployment discipline as they do on models.
To understand where cloud platform knowledge fits into this space, read Cloud Skills in 2026: AWS, Azure, and GCP Roles in Highest Demand. Earth observation pipelines live or die on storage, networking, orchestration, and cost-aware infrastructure design.
For readers deciding which data role fits them best, Data Analyst vs Data Scientist vs Data Engineer: Career Guide is worth reading next. Earth observation projects often blur those boundaries, so understanding the differences can help you choose the right learning path.
You may also want to read Data Science & AI Bootcamp Syllabus 2026: Python, MLOps, and AI Agents, What Is a Data Science Bootcamp? 2026 Guide, and Choosing a Data Science Bootcamp in 2026: Complete Guide for a more structured roadmap from self-study into portfolio-ready work.