docker-image-cleanup 0.1.3__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: docker-image-cleanup
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary: Clean up old Docker images from local storage with retention controls
5
5
  Keywords: docker,cleanup,images,disk-space
6
6
  Author: Michael Bianco
@@ -89,6 +89,18 @@ docker run -d \
89
89
  ghcr.io/iloveitaly/docker-image-cleanup:latest
90
90
  ```
91
91
 
92
+ Here's an example script to get the latest image and restart any existing docker image:
93
+
94
+ ```
95
+ docker pull ghcr.io/iloveitaly/docker-image-cleanup:latest
96
+ docker stop docker-image-cleanup && docker rm docker-image-cleanup
97
+ docker run -d \
98
+ --name docker-image-cleanup \
99
+ -v /var/run/docker.sock:/var/run/docker.sock \
100
+ -e TARGET_REPOS="repo.com/one repo.com/two" \
101
+ ghcr.io/iloveitaly/docker-image-cleanup:latest
102
+ ```
103
+
92
104
  ### Docker Compose
93
105
 
94
106
  ```yaml
@@ -132,4 +144,3 @@ Images with multiple tags are handled intelligently. If all tags on an image wou
132
144
  ---
133
145
 
134
146
  *This project was created from [iloveitaly/python-package-template](https://github.com/iloveitaly/python-package-template)*
135
- > theirs
@@ -73,6 +73,18 @@ docker run -d \
73
73
  ghcr.io/iloveitaly/docker-image-cleanup:latest
74
74
  ```
75
75
 
76
+ Here's an example script to get the latest image and restart any existing docker image:
77
+
78
+ ```
79
+ docker pull ghcr.io/iloveitaly/docker-image-cleanup:latest
80
+ docker stop docker-image-cleanup && docker rm docker-image-cleanup
81
+ docker run -d \
82
+ --name docker-image-cleanup \
83
+ -v /var/run/docker.sock:/var/run/docker.sock \
84
+ -e TARGET_REPOS="repo.com/one repo.com/two" \
85
+ ghcr.io/iloveitaly/docker-image-cleanup:latest
86
+ ```
87
+
76
88
  ### Docker Compose
77
89
 
78
90
  ```yaml
@@ -116,4 +128,3 @@ Images with multiple tags are handled intelligently. If all tags on an image wou
116
128
  ---
117
129
 
118
130
  *This project was created from [iloveitaly/python-package-template](https://github.com/iloveitaly/python-package-template)*
119
- > theirs
@@ -2,6 +2,7 @@
2
2
  Cleans up old Docker images from local storage with retention controls.
3
3
  """
4
4
 
5
+ import sys
5
6
  from collections import defaultdict
6
7
  from typing import Any
7
8
 
@@ -113,7 +114,7 @@ def determine_cleanup_actions(
113
114
  if img.created >= min_age_threshold:
114
115
  keep_tags.update(img.tags)
115
116
 
116
- images_to_delete_ids: set[str] = set()
117
+ images_to_delete: dict[str, int] = {}
117
118
  tags_to_remove: list[str] = []
118
119
 
119
120
  images_by_id: dict[str, list[ImageInfo]] = defaultdict(list)
@@ -142,7 +143,7 @@ def determine_cleanup_actions(
142
143
  if set(tags_for_this_image_to_remove) == current_image_repo_tags:
143
144
  img_info = img_list[0]
144
145
  if not img_info.is_in_use:
145
- images_to_delete_ids.add(img_id)
146
+ images_to_delete[img_id] = img_info.size
146
147
  total_size_saved += img_info.size
147
148
  else:
148
149
  log.warning(
@@ -152,7 +153,7 @@ def determine_cleanup_actions(
152
153
  )
153
154
 
154
155
  return {
155
- "images_to_delete": list(images_to_delete_ids),
156
+ "images_to_delete": images_to_delete,
156
157
  "tags_to_remove": list(set(tags_to_remove)),
157
158
  "total_size_saved": total_size_saved,
158
159
  }
@@ -160,7 +161,7 @@ def determine_cleanup_actions(
160
161
 
161
162
  def execute_cleanup(
162
163
  client: docker.DockerClient,
163
- images_to_delete: list[str],
164
+ images_to_delete: dict[str, int],
164
165
  tags_to_remove: list[str],
165
166
  total_size_saved: int,
166
167
  dry_run: bool,
@@ -172,7 +173,7 @@ def execute_cleanup(
172
173
  if tags_to_remove:
173
174
  log.info("would remove tags", tags=tags_to_remove)
174
175
  if images_to_delete:
175
- log.info("would remove images", image_ids=images_to_delete)
176
+ log.info("would remove images", image_ids=list(images_to_delete.keys()))
176
177
 
177
178
  log.info(
178
179
  "total space that would be saved",
@@ -184,19 +185,25 @@ def execute_cleanup(
184
185
  for tag in tags_to_remove:
185
186
  try:
186
187
  client.images.remove(tag, force=False)
187
- log.info("untagged image", tag=tag)
188
+ log.info("removed tag", tag=tag)
188
189
  except docker.errors.APIError as e:
189
- log.warning("skipped untagging", tag=tag, reason=str(e))
190
+ log.warning("skipped tag removal", tag=tag, reason=str(e))
190
191
 
191
- for img_id in images_to_delete:
192
+ # We explicitly remove the Image ID here even after untagging.
193
+ # This is primarily to catch and clean up "dangling" images (images with no tags)
194
+ # that might have been orphaned but wouldn't be caught by the tag loop.
195
+ for img_id, size in images_to_delete.items():
192
196
  try:
193
- img = client.images.get(img_id)
194
- size = img.attrs.get("Size", 0)
195
197
  client.images.remove(img_id, force=False)
196
198
  log.info("removed image", image_id=img_id, size=format_size(size))
197
199
  executed_size_saved += size
198
200
  except docker.errors.ImageNotFound:
199
- log.warning("image already removed", image_id=img_id)
201
+ # If the image is not found here, it means Docker automatically deleted
202
+ # the underlying image data when we removed its last tag in the loop above.
203
+ log.info(
204
+ "image removed via untagging", image_id=img_id, size=format_size(size)
205
+ )
206
+ executed_size_saved += size
200
207
  except docker.errors.APIError as e:
201
208
  log.warning("skipped image removal", image_id=img_id, reason=str(e))
202
209
 
@@ -264,6 +271,13 @@ def main(
264
271
  dry_run=dry_run,
265
272
  )
266
273
 
274
+ log.info(
275
+ "cleanup configuration",
276
+ num_recent=config.num_recent,
277
+ min_age_days=config.min_age_days,
278
+ dry_run=config.dry_run,
279
+ )
280
+
267
281
  grand_total_saved = 0
268
282
  for repo_name in image_repos:
269
283
  log.info("processing repository", repo=repo_name)
@@ -298,10 +312,10 @@ def main(
298
312
 
299
313
  except docker.errors.DockerException as e:
300
314
  log.error("failed to connect to Docker daemon", reason=str(e))
301
- exit(1)
315
+ sys.exit(1)
302
316
  except Exception:
303
317
  log.exception("an unexpected error occurred")
304
- exit(1)
318
+ sys.exit(1)
305
319
 
306
320
 
307
321
  if __name__ == "__main__":
@@ -0,0 +1,65 @@
1
+ [project]
2
+ name = "docker-image-cleanup"
3
+ version = "0.2.0"
4
+ description = "Clean up old Docker images from local storage with retention controls"
5
+ keywords = [
6
+ "docker",
7
+ "cleanup",
8
+ "images",
9
+ "disk-space",
10
+ ]
11
+ readme = "README.md"
12
+ requires-python = ">=3.11"
13
+ dependencies = [
14
+ "click>=8.3.0",
15
+ "docker>=7.1.0",
16
+ "pydantic>=2.12.5",
17
+ "structlog-config>=0.6.0",
18
+ "whenever>=0.9.3",
19
+ ]
20
+
21
+ [[project.authors]]
22
+ name = "Michael Bianco"
23
+ email = "mike@mikebian.co"
24
+
25
+ [project.urls]
26
+ Repository = "https://github.com/iloveitaly/docker-image-cleanup"
27
+
28
+ [project.scripts]
29
+ docker-image-cleanup = "docker_image_cleanup:main"
30
+
31
+ [build-system]
32
+ requires = ["uv_build>=0.11.0,<0.13"]
33
+ build-backend = "uv_build"
34
+
35
+ [tool.uv.build-backend]
36
+ module-root = ""
37
+
38
+ [tool.pyright]
39
+ exclude = [
40
+ "examples/",
41
+ "playground/",
42
+ "tmp/",
43
+ ".venv/",
44
+ "tests/",
45
+ ]
46
+
47
+ [tool.pytest.ini_options]
48
+ addopts = "--cov --cov-report=term-missing --cov-report=html:tmp/htmlcov"
49
+
50
+ [tool.coverage.run]
51
+ plugins = ["covdefaults"]
52
+ source = ["docker_image_cleanup"]
53
+
54
+ [tool.coverage.report]
55
+ fail_under = 50
56
+
57
+ [dependency-groups]
58
+ dev = [
59
+ "pytest>=8.3.4",
60
+ "pyright[nodejs]>=1.1.408",
61
+ "ruff>=0.8.0",
62
+ "coverage>=7.13.4",
63
+ "pytest-cov>=7.0.0",
64
+ "covdefaults>=2.3.0",
65
+ ]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "docker-image-cleanup"
3
- version = "0.1.3"
3
+ version = "0.2.0"
4
4
  description = "Clean up old Docker images from local storage with retention controls"
5
5
  keywords = ["docker", "cleanup", "images", "disk-space"]
6
6
  readme = "README.md"
@@ -22,7 +22,7 @@ docker-image-cleanup = "docker_image_cleanup:main"
22
22
 
23
23
 
24
24
  [build-system]
25
- requires = ["uv_build>=0.11.0,<0.12"]
25
+ requires = ["uv_build>=0.11.0,<0.13"]
26
26
  build-backend = "uv_build"
27
27
 
28
28
  [tool.uv.build-backend]