diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 9422790..e34dfcf 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -19,11 +19,13 @@ Added
 *****
 - ``raster.Image()``: With Statement Context Manager for Image #45
 - ``raster.Image()``: Alter image array #67
+- ``raster.Image()``: Target align option for `warp()` #60
 
 Fixed
 *****
 - ``data.Source()``: Fixed query metadata return for new EarthExplorer API #71
 - ``raster.Image()``: Consider all image bands in `pad()` #59
+- ``raster.Image()``: Memory leak caused by `__update_dataset()` #62
 
 Changed
 *******
diff --git a/requirements.txt b/requirements.txt
index 0939c24..5e42d44 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
 shapely~=1.7.0
 numpy~=1.18.1
 requests~=2.23.0
-rasterio~=1.1.0
+rasterio>=1.1.0,<1.1.5
 pillow~=7.1.2
 sentinelsat~=0.13
 landsatxplore==0.6
diff --git a/setup.py b/setup.py
index afb5073..a92da5f 100644
--- a/setup.py
+++ b/setup.py
@@ -39,7 +39,7 @@ extras_require = {
         "python-dateutil~=2.8.1",
         "shapely~=1.7.0",
     ],
-    "raster": ["numpy~=1.18.1", "rasterio~=1.1.0", "rio_toa==0.3.0", "shapely~=1.7.0",],
+    "raster": ["numpy~=1.18.1", "rasterio!=1.1.5", "rio_toa==0.3.0", "shapely~=1.7.0",],
 }
 extras_require["complete"] = list(set([v for req in extras_require.values() for v in req]))
 
diff --git a/ukis_pysat/raster.py b/ukis_pysat/raster.py
index 97d0dc7..c4b3176 100644
--- a/ukis_pysat/raster.py
+++ b/ukis_pysat/raster.py
@@ -57,8 +57,9 @@ class Image:
             if dimorder == "first":
                 self.__arr = data
             else:
-                self.__arr = reshape_as_raster(data)
-            self.dataset = self.__update_dataset(crs, transform)
+                self.__arr = reshape_as_raster(data)
+            self.dataset = None
+            self.__update_dataset(crs, transform)
         else:
             raise TypeError("dataset must be of type rasterio.io.DatasetReader, str or np.ndarray")
 
@@ -114,6 +115,7 @@ class Image:
         """
         if pad:
             from warnings import warn
+
             warn("`pad` was renamed to `fill` in `mask()` and will be removed with version 0.7.0", DeprecationWarning)
             fill = pad
 
@@ -122,7 +124,7 @@ class Image:
             pad_width = self._get_pad_width(bbox)
             if pad_width > 0:
                 # only pad raster if it is smaller than bbox
-                self.dataset = self.pad(pad_width, mode, constant_values)
+                self.pad(pad_width, mode, constant_values)
 
         if isinstance(bbox, polygon.Polygon):
             self.__arr, transform = rasterio.mask.mask(self.dataset, [bbox], crop=crop)
@@ -131,10 +133,7 @@ class Image:
         else:
             raise TypeError(f"bbox must be of type tuple or Shapely Polygon")
 
-        # update for further processing
-        self.dataset.close()  # meta still available when DataSetReader is closed
-        # passing meta to open, because driver gets lost if it's not GTiff
-        self.dataset = self.__update_dataset(self.dataset.crs, transform, nodata=self.dataset.nodata)
+        self.__update_dataset(self.dataset.crs, transform, nodata=self.dataset.nodata)
 
     def _get_pad_width(self, bbox):
         """Calculates biggest difference from raster bounds to bbox. Can be used with pad() to fill array with
@@ -175,9 +174,7 @@ class Image:
             )
 
         self.__arr = destination
-
-        self.dataset.close()
-        return self.__update_dataset(self.dataset.crs, transform, nodata=self.dataset.nodata)
+        self.__update_dataset(self.dataset.crs, transform, nodata=self.dataset.nodata)
 
     def __update_dataset(self, crs, transform, nodata=None):
         """Update dataset without writing to file after it theoretically changed.
@@ -197,15 +194,15 @@ class Image:
             "count": self.__arr.shape[0],
             "crs": crs,
             "transform": transform,
-        }
-
-        memfile = MemoryFile()
-        with memfile.open(**meta) as ds:
-            ds.write(self.__arr)
-
-        return memfile.open()
-
-    def warp(self, dst_crs, resampling_method=0, num_threads=4, resolution=None, nodata=None):
+        }
+
+        memfile = MemoryFile()
+        with memfile.open(**meta) as ds:
+            ds.write(self.__arr)
+        self.dataset = memfile.open()
+        memfile.close()
+
+    def warp(self, dst_crs, resampling_method=0, num_threads=4, resolution=None, nodata=None, target_align=None):
         """Reproject a source raster to a destination raster.
 
         :param dst_crs: CRS or dict, Target coordinate reference system.
@@ -214,22 +211,28 @@ class Image:
         :param num_threads: int, number of workers, optional (default: 4)
         :param resolution: tuple (x resolution, y resolution) or float, optional.
             Target resolution, in units of target coordinate reference system.
+        :param target_align: raster to which to align resolution, extent and gridspacing, optional (Image).
         :param nodata: nodata value of source, int or float, optional.
-        """
-        # output dimensions and transform for reprojection.
-        if resolution:
-            transform, width, height = calculate_default_transform(
-                self.dataset.crs,
-                dst_crs,
-                self.dataset.width,
-                self.dataset.height,
-                *self.dataset.bounds,
-                resolution=resolution,
-            )
+        """
+        if target_align:
+            transform = target_align.dataset.transform
+            width = target_align.dataset.width
+            height = target_align.dataset.height
+
         else:
-            transform, width, height = calculate_default_transform(
-                self.dataset.crs, dst_crs, self.dataset.width, self.dataset.height, *self.dataset.bounds,
-            )
+            if resolution:
+                transform, width, height = calculate_default_transform(
+                    self.dataset.crs,
+                    dst_crs,
+                    self.dataset.width,
+                    self.dataset.height,
+                    *self.dataset.bounds,
+                    resolution=resolution,
+                )
+            else:
+                transform, width, height = calculate_default_transform(
+                    self.dataset.crs, dst_crs, self.dataset.width, self.dataset.height, *self.dataset.bounds,
+                )
 
         destination = np.zeros((self.dataset.count, height, width), self.__arr.dtype)
 
@@ -246,8 +249,7 @@ class Image:
             num_threads=num_threads,
         )
 
-        self.dataset.close()
-        self.dataset = self.__update_dataset(dst_crs, transform, nodata=nodata)
+        self.__update_dataset(dst_crs, transform, nodata=nodata)
 
     def dn2toa(self, platform, mtl_file=None, wavelengths=None):
         """This method converts digital numbers to top of atmosphere reflectance, like described here:
@@ -317,8 +319,7 @@ class Image:
                 f"Sentinel-2]. "
             )
 
-        self.dataset.close()
-        self.dataset = self.__update_dataset(self.dataset.crs, self.dataset.transform, nodata=self.dataset.nodata)
+        self.__update_dataset(self.dataset.crs, self.dataset.transform, nodata=self.dataset.nodata)
 
     @staticmethod
     def _lookup_bands(platform, wavelengths):

Target align option for Image.warp
In many applications it is extremely useful to be able to force align two raster grids to match each others crs, extent, dimensions and grid alignment. With a combination of `warp` and `mask` we can come close to align two rasters (this actually works in many cases). It may, however, happen that the raster grids are shifted with subpixel resolution, which prevents them from being exactly on the same grid and therefore they cannot be compared at array level with for example numpy. 

I propose the following solution to add a `target_align` option to `warp`, which aligns one Image instance to another one:

```python
    def warp(self, dst_crs, resampling_method=0, num_threads=4, resolution=None, nodata=None, target_align=None):
        """Reproject a source raster to a destination raster.
        :param dst_crs: CRS or dict, Target coordinate reference system.
        :param resampling_method: Resampling algorithm, int, defaults to 0 (Nearest)
            numbers: https://github.com/mapbox/rasterio/blob/master/rasterio/enums.py#L28
        :param num_threads: int, number of workers, optional (default: 4)
        :param resolution: tuple (x resolution, y resolution) or float, optional.
            Target resolution, in units of target coordinate reference system.
        :param target_align: raster to which to align resolution, extent and gridspacing, optional (Image).
        :param nodata: nodata value of source, int or float, optional.
        """
        if target_align:
            transform = target_align.dataset.transform
            width = target_align.dataset.width
            height = target_align.dataset.height

        else:
            if resolution:
                transform, width, height = calculate_default_transform(
                    self.dataset.crs,
                    dst_crs,
                    self.dataset.width,
                    self.dataset.height,
                    *self.dataset.bounds,
                    resolution=resolution,
                )

            else:
                transform, width, height = calculate_default_transform(
                    self.dataset.crs, dst_crs, self.dataset.width, self.dataset.height, *self.dataset.bounds,
                )

        destination = np.zeros((self.dataset.count, height, width), self.__arr.dtype)

        self.__arr, transform = reproject(
            source=self.__arr,
            destination=destination,
            src_transform=self.dataset.transform,
            src_crs=self.dataset.crs,
            src_nodata=nodata,
            dst_transform=transform,
            dst_crs=dst_crs,
            dst_nodata=nodata,
            resampling=resampling_method,
            num_threads=num_threads,
        )

        self.dataset.close()
        self.dataset = self.__update_dataset(dst_crs, transform, nodata=nodata)
```
diff --git a/tests/test_raster.py b/tests/test_raster.py
index 0bf67ca..1f65f72 100644
--- a/tests/test_raster.py
+++ b/tests/test_raster.py
@@ -175,6 +175,14 @@ class DataTest(unittest.TestCase):
         self.img.warp("EPSG:4326", resolution=1.0)
         self.assertEqual(1.0, self.img.dataset.transform.to_gdal()[1])
 
+        source_img = Image(TEST_FILE)
+        source_img.warp("EPSG:3857", resolution=10)
+        target_img = Image(TEST_FILE)
+        target_img.warp("EPSG:3857", resolution=25)
+        self.assertNotEqual(source_img.dataset.transform, target_img.dataset.transform)
+        source_img.warp("EPSG:3857", target_align=target_img)
+        self.assertEqual(source_img.dataset.transform, target_img.dataset.transform)
+
     def test_dn2toa(self):
         target_dir = os.path.join(os.path.dirname(__file__), "testfiles", "satellite_data")
         tests = [

pip install -e .[complete]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
affine==2.4.0
appdirs==1.4.4
attrs==24.2.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
chardet==3.0.4
click==7.1.2
click-plugins==1.1.1
cligj==0.7.2
dask==2.14.0
decopatch==1.4.10
exceptiongroup==1.2.2
Fiona==1.8.22
geographiclib==2.0
geojson==3.2.0
geomet==1.1.0
geopy==2.4.1
html2text==2020.1.16
idna==2.10
importlib-metadata==6.7.0
iniconfig==2.0.0
landsatxplore==0.6
makefun==1.15.6
munch==4.0.0
numpy==1.18.5
packaging==24.0
pandas==1.0.5
Pillow==7.1.2
pluggy==1.2.0
pyfields==1.1.5
pylandsat==0.4
pyparsing==3.1.4
pyproj==2.6.1.post1
pytest==7.4.4
python-dateutil==2.8.2
pytz==2025.2
rasterio==1.1.8
requests==2.23.0
rio-mucho==1.0.0
rio-toa==0.3.0
sentinelsat==0.14
Shapely==1.7.1
six==1.17.0
snuggs==1.4.7
tomli==2.0.1
toolz==0.12.1
tqdm==4.67.1
typing_extensions==4.7.1
-e git+https://github.com/dlr-eoc/ukis-pysat.git@e708895f916919af8b52f963d45c10e4c2b001e9#egg=ukis_pysat
urllib3==1.25.11
utm==0.5.0
valid8==5.1.2
zipp==3.15.0

name: ukis-pysat
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - affine==2.4.0
      - appdirs==1.4.4
      - attrs==24.2.0
      - chardet==3.0.4
      - click==7.1.2
      - click-plugins==1.1.1
      - cligj==0.7.2
      - dask==2.14.0
      - decopatch==1.4.10
      - exceptiongroup==1.2.2
      - fiona==1.8.22
      - geographiclib==2.0
      - geojson==3.2.0
      - geomet==1.1.0
      - geopy==2.4.1
      - html2text==2020.1.16
      - idna==2.10
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - landsatxplore==0.6
      - makefun==1.15.6
      - munch==4.0.0
      - numpy==1.18.5
      - packaging==24.0
      - pandas==1.0.5
      - pillow==7.1.2
      - pluggy==1.2.0
      - pyfields==1.1.5
      - pylandsat==0.4
      - pyparsing==3.1.4
      - pyproj==2.6.1.post1
      - pytest==7.4.4
      - python-dateutil==2.8.2
      - pytz==2025.2
      - rasterio==1.1.8
      - requests==2.23.0
      - rio-mucho==1.0.0
      - rio-toa==0.3.0
      - sentinelsat==0.14
      - shapely==1.7.1
      - six==1.17.0
      - snuggs==1.4.7
      - tomli==2.0.1
      - toolz==0.12.1
      - tqdm==4.67.1
      - typing-extensions==4.7.1
      - urllib3==1.25.11
      - utm==0.5.0
      - valid8==5.1.2
      - zipp==3.15.0
prefix: /opt/conda/envs/ukis-pysat

tests/test_raster.py::DataTest::test_warp
tests/test_raster.py::DataTest::test__lookup_bands tests/test_raster.py::DataTest::test_arr tests/test_raster.py::DataTest::test_context tests/test_raster.py::DataTest::test_dimorder_error tests/test_raster.py::DataTest::test_dn2toa tests/test_raster.py::DataTest::test_get_dask_array tests/test_raster.py::DataTest::test_get_subset tests/test_raster.py::DataTest::test_get_tiles tests/test_raster.py::DataTest::test_get_valid_data_bbox tests/test_raster.py::DataTest::test_init tests/test_raster.py::DataTest::test_init_fail_invalid_dataset tests/test_raster.py::DataTest::test_init_fail_invalid_path tests/test_raster.py::DataTest::test_init_with_array tests/test_raster.py::DataTest::test_init_with_arry_fail_missing_crs tests/test_raster.py::DataTest::test_init_with_arry_fail_missing_crs_and_transform tests/test_raster.py::DataTest::test_init_with_arry_fail_missing_transform tests/test_raster.py::DataTest::test_mask_image tests/test_raster.py::DataTest::test_set_array tests/test_raster.py::DataTest::test_set_array_error tests/test_raster.py::DataTest::test_write_to_file
setup.py CHANGELOG.rst ukis_pysat/raster.py requirements.txt
setup.py CHANGELOG.rst ukis_pysat/raster.py requirements.txt