Short answer

A GDAL VRT (Virtual Raster) is a lightweight XML file that tells GDAL how to present one or more source rasters as a single dataset — without copying a single pixel. You build one with gdalbuildvrt mosaic.vrt tiles/*.tif, and every GDAL-aware tool (gdalwarp, gdaldem, QGIS, rasterio) can then read the VRT as if it were one big raster. VRTs are the right tool for mosaicking tile sets, stacking bands, and chaining virtual operations cheaply; they are the wrong tool when you need fast, repeated reads, because each access still touches the underlying files.

What is inside a VRT

A VRT is human-readable XML. Open one and you will see the virtual dataset's size and geotransform, its CRS in <SRS>, and for each band a list of <SimpleSource> or <ComplexSource> elements. Each source names a file and maps a rectangle of that file (<SrcRect>) onto a rectangle of the virtual raster (<DstRect>). Because all of this is metadata, building a VRT over thousands of tiles takes a fraction of a second and the file is a few kilobytes regardless of how many terabytes it references.

That design makes VRTs composable. The output of one gdalbuildvrt can be the input to a gdalwarp -of VRT (virtual reprojection), which can feed a gdal_translate -of VRT band selection, and only the final gdal_translate -of GTiff actually reads and writes pixels. The whole chain stays virtual until you decide to materialise it.

Mosaicking tiles: the core use case

The most common job is stitching a directory of adjacent tiles — Copernicus DEM granules, an orthophoto delivery, Sentinel-2 scenes — into one dataset.

gdalbuildvrt dem_mosaic.vrt dem_tiles/*.tif

For large tile sets, command-line globbing breaks; pass a file list instead:

ls dem_tiles/*.tif > tiles.txt
gdalbuildvrt -input_file_list tiles.txt dem_mosaic.vrt

Useful mosaic flags:

  • -resolutionhighest (default for finest input), lowest, average, or user with -tr xres yres. Mixing resolutions silently resamples to the chosen target, so set this deliberately.
  • -r — resampling method used when sources differ in resolution or alignment (nearest, bilinear, cubic). Use bilinear/cubic for continuous data like elevation, nearest for categorical rasters.
  • -addalpha / -srcnodata / -vrtnodata — control transparency and NoData. Overlapping tiles in a VRT are painted in input order (last source wins); set NoData correctly so black collars do not overwrite real data at seams.
  • -allow_projection_difference — only documents the mismatch; it does not reproject. Tiles in different CRS still need gdalwarp first.

Stacking bands with -separate

A different mode treats each input as one band of the output. This is how you assemble a multi-band stack from per-band files (a classic Landsat/Sentinel layout):

gdalbuildvrt -separate s2_stack.vrt B04.jp2 B03.jp2 B02.jp2

This produces a 3-band virtual raster (red, green, blue in that order) without rewriting the imagery. You can then gdal_translate it to an RGB GeoTIFF, or feed it straight into NDVI math.

A complete worked workflow

Goal: take ~600 Copernicus 30 m DEM tiles in EPSG:4326, produce a single reprojected, overviewed, Cloud-Optimised GeoTIFF (COG) in UTM for a regional terrain model.

  1. List and inspect. Confirm every tile shares the CRS and band count:
ls dem_tiles/*.tif > tiles.txt
gdalinfo $(head -1 tiles.txt) | grep -E "Coordinate System|Band"
  1. Build the mosaic VRT (virtual, instant), preserving native resolution and NoData:
gdalbuildvrt -input_file_list tiles.txt -srcnodata 0 \
  -vrtnodata 0 -r bilinear dem_mosaic.vrt
  1. Virtually reproject to UTM without writing pixels yet:
gdalwarp -t_srs EPSG:32632 -tr 30 30 -r cubic \
  -of VRT dem_mosaic.vrt dem_utm.vrt
  1. Materialise once to a tiled, compressed COG. This is the only step that reads all the pixels:
gdal_translate -of COG -co COMPRESS=DEFLATE -co PREDICTOR=2 \
  -co BLOCKSIZE=512 dem_utm.vrt dem_region_cog.tif
  1. Build overviews if you used plain GTiff instead of COG (COG builds them internally):
gdaladdo -r average dem_region_cog.tif 2 4 8 16 32

The win here is sequencing: steps 2–3 cost milliseconds and let you preview the result in QGIS before committing the expensive write in step 4.

When a VRT helps and when it hurts

Use a VRT when:

  • You need a single logical dataset over many tiles for analysis or preview.
  • You are chaining transformations and want to defer the costly pixel write.
  • The data updates and you want the "mosaic" to pick up new tiles by rebuilding a tiny XML file.

Materialise a real raster when:

  • The dataset is read repeatedly under load (a web tile service). Every read of a VRT-over-many-tiles opens many file handles and seeks; one COG with internal tiling and overviews is far faster.
  • You will distribute the data. A VRT is useless to someone who does not also have every source file at the exact relative or absolute paths it records.

That last point is the most common operational trap.

Common pitfalls and why they happen

  • VRT works on your machine, breaks elsewhere. The XML stores source paths. If they are relative, the VRT must travel with its tiles in the same layout; if absolute, it breaks the moment files move. Use gdalbuildvrt from the tile directory and keep relative paths, or never ship a VRT as a deliverable.
  • gdalbuildvrt refuses mixed inputs. It needs a common CRS and compatible bands. Reproject with gdalwarp first; for genuinely different band counts decide whether you want a mosaic (same bands) or a stack (-separate).
  • Seams or black wedges at tile boundaries. NoData is not declared, so collar pixels overwrite neighbours. Set -srcnodata/-vrtnodata to match the source fill value.
  • "Successful" output that is empty or wrong-resolution. The default -resolution highest picked an unexpected finest tile, or near resampling stair-stepped continuous data. Set -resolution and -r explicitly.
  • Slow analysis over the VRT. Expected — you are reading hundreds of files. Materialise a COG for anything performance-sensitive.

Validation

After building, treat the VRT like any raster:

gdalinfo -stats dem_mosaic.vrt

Check the reported extent covers the whole study area, the band count is right, and the statistics are plausible (no all-zero band, no min/max that betrays a NoData problem). Open it in QGIS and pan to several tile boundaries — seams, resolution jumps, or NoData wedges show up there first. Confirm the source count with grep -c SourceFilename dem_mosaic.vrt against the number of input tiles.

Bathyl perspective

We use VRTs as the cheap connective tissue of a raster pipeline: mosaic, reproject, and stack virtually, preview, then materialise a single Cloud-Optimised GeoTIFF for anything that ships or serves. The rule we hold to is that a VRT is a build artifact, never a deliverable, because it silently depends on files a client may never have.

Related reading

Sources