With gdal and numpy the following is possible assuming the images are the same size (rows and columns) and have the same CRS (e.g. 10m bands from a Sentinel 2 scene).
import gdal
import numpy as np
file_list = ['band1.tif', 'band2.tif', 'band3.tif']
array_list = []
# Read arrays
for file in file_list:
src = gdal.Open(file)
geotransform = src.GetGeoTransform() # Could be done more elegantly outside the for loop
projection = src.GetProjectionRef()
array_list.append(src.ReadAsArray())
src = None
# Stack arrays
stacked_array = np.stack(array_list, axis=0)
array_list = None
# Write to disk
driver = gdal.GetDriverByName('GTiff')
n, rows, cols = stacked_array.shape
dataset = driver.Create('output_file_name.tif', cols, rows, n,
gdal.GDT_Uint16)
dataset.SetGeoTransform(geotransform)
dataset.SetProjection(projection)
for b in range(1,n+1):
band = dataset.GetRasterBand(b) # GetRasterBand is not zero indexed
band.WriteArray(stacked_array[b-1]) # Numpy is zero indexed
dataset = None
stacked_array = None