Merge pull request 'Initialer Release (v1.0.0)' (#1) from initial into main

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-02-18 22:10:12 +00:00
3 changed files with 540 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 scriptos
Copyright (c) 2026 Patrick Asmus (scriptos)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@@ -1,9 +1,65 @@
# template_repository
# Docker Image Manager
Ein interaktives Bash-Script zum Anzeigen, Filtern und Loeschen von Docker Images. Es listet alle Images inklusive zugeordneter Container, zeigt ungenutzte Images und erlaubt das gezielte Loeschen per Nummer oder den sicheren Massenvorgang per Parameter.
## Voraussetzungen
Wichtig: Link für Lizenz anpassen.
- Docker installiert und laeuft
- Bash (unter Windows z.B. via WSL oder Git Bash)
## Nutzung
```bash
bash ./docker-image-manager.v1.sh
```
Ohne Parameter startet das interaktive Menu. Dort kannst du Images anzeigen und optional direkt Details zu einem Image abrufen.
### Befehle
- `list` - Alle Docker Images auflisten (inkl. zugeordneter Container)
- `unused-images` - Images anzeigen, die von keinem Container genutzt werden
- `inspect` - Details zu einem Image anzeigen (Auswahl per Nummer, inkl. Containerliste)
- `delete` - Einzelne Images nach Nummer loeschen
- `purge-unused` - Alle ungenutzten Images loeschen
- `help` / `-h` / `--help` - Hilfe anzeigen
### Optionen
- `--yes` - Loeschen ohne Rueckfrage
## Beispiele
Alle Images anzeigen:
```bash
bash ./docker-image-manager.v1.sh list
```
Alle ungenutzten Images loeschen (mit Rueckfrage):
```bash
bash ./docker-image-manager.v1.sh purge-unused
```
Ein Image nach Nummer loeschen:
```bash
bash ./docker-image-manager.v1.sh delete 3
```
Image Details inkl. zugeordneter Container anzeigen:
```bash
bash ./docker-image-manager.v1.sh inspect 2
```
## Hinweise
- Ungenutzte Images werden ueber alle Container ermittelt (inkl. gestoppter Container).
- Ungetaggte (dangling) Images haben in der Liste die Tags `<none>:<none>` und tauchen bei "ungenutzt" auf, wenn kein Container sie nutzt.
- In der Liste zeigt die Spalte `CONTAINERS`, welche Container das Image verwenden.
- Das Script loescht keine Images, die noch von Containern genutzt werden (weder einzeln noch als Massenloeschung). In diesem Fall werden die Container angezeigt und das Image uebersprungen.
<p align="center">
@@ -11,5 +67,5 @@ Wichtig: Link für Lizenz anpassen.
</p>
<p align="center">
<img src="https://assets.techniverse.net/f1/logos/small/license.png" alt="License" width="15" height="15"> <a href="./template_repository/src/branch/main/LICENSE">License</a> | <img src="https://assets.techniverse.net/f1/logos/small/matrix2.svg" alt="Matrix" width="15" height="15"> <a href="https://matrix.to/#/#community:techniverse.net">Matrix</a> | <img src="https://assets.techniverse.net/f1/logos/small/mastodon2.svg" alt="Matrix" width="15" height="15"> <a href="https://social.techniverse.net/@donnerwolke">Mastodon</a>
<img src="https://assets.techniverse.net/f1/logos/small/license.png" alt="License" width="15" height="15"> <a href="./LICENSE">License</a> | <img src="https://assets.techniverse.net/f1/logos/small/matrix2.svg" alt="Matrix" width="15" height="15"> <a href="https://matrix.to/#/#community:techniverse.net">Matrix</a> | <img src="https://assets.techniverse.net/f1/logos/small/mastodon2.svg" alt="Matrix" width="15" height="15"> <a href="https://social.techniverse.net/@donnerwolke">Mastodon</a>
</p>

480
docker-image-manager.v1.sh Normal file
View File

@@ -0,0 +1,480 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
# Script Name: docker-image-manager.v1.sh
# Beschreibung: Docker Image Manager - verwaltet und inspiziert Docker Images
# - Alle Images auflisten
# - Ungenutzte Images anzeigen
# - Zeigt zugeordnete Container pro Image
# - Images interaktiv loeschen
# Autor: Patrick Asmus
# Web: https://www.cleveradmin.de
# Git-Reposit.: https://git.techniverse.net/scriptos/docker-image-manager
# Version: 1.0.0
# Datum: 18.02.2026
# Modifikation: Initialer Release (v1.0.0)
#####################################################
LAST_IDS=()
LAST_LABELS=()
declare -A IMAGE_TO_CONTAINERS=()
SELECTED_IDS=()
SELECTED_LABELS=()
require_docker() {
if ! command -v docker >/dev/null 2>&1; then
echo "Docker CLI nicht gefunden. Bitte Docker installieren und PATH pruefen." >&2
exit 1
fi
if ! docker info >/dev/null 2>&1; then
echo "Docker Daemon nicht erreichbar. Bitte Docker starten." >&2
exit 1
fi
}
load_container_map() {
IMAGE_TO_CONTAINERS=()
local -a lines
mapfile -t lines < <(docker ps -a -q | xargs -r docker inspect --format '{{.Id}}|{{.Image}}|{{.Name}}')
if [[ ${#lines[@]} -eq 0 ]]; then
return 0
fi
local line image_id name
for line in "${lines[@]}"; do
IFS='|' read -r _ image_id name <<< "$line"
name=${name#/}
if [[ -n "${IMAGE_TO_CONTAINERS[$image_id]:-}" ]]; then
IMAGE_TO_CONTAINERS[$image_id]="${IMAGE_TO_CONTAINERS[$image_id]},${name}"
else
IMAGE_TO_CONTAINERS[$image_id]="$name"
fi
done
}
print_images_table() {
if [[ ${#LAST_IDS[@]} -eq 0 ]]; then
echo "Keine Images gefunden."
return 0
fi
load_container_map
printf "%-4s %-30s %-20s %-15s %-12s %-20s %-30s\n" "Nr." "REPOSITORY" "TAG" "IMAGE ID" "SIZE" "CREATED" "CONTAINERS"
printf "%-4s %-30s %-20s %-15s %-12s %-20s %-30s\n" "----" "------------------------------" "--------------------" "---------------" "------------" "--------------------" "------------------------------"
for i in "${!LAST_IDS[@]}"; do
IFS='|' read -r repo tag short_id size created <<< "${LAST_LABELS[$i]}"
local containers="${IMAGE_TO_CONTAINERS[${LAST_IDS[$i]}]:-}"
if [[ -z "$containers" ]]; then
containers="-"
fi
printf "%-4s %-30s %-20s %-15s %-12s %-20s %-30s\n" "$((i + 1))" "$repo" "$tag" "$short_id" "$size" "$created" "$containers"
done
}
load_images_from_lines() {
local -a lines=("$@")
LAST_IDS=()
LAST_LABELS=()
if [[ ${#lines[@]} -eq 0 ]]; then
return 0
fi
local line id repo tag created size short_id
for line in "${lines[@]}"; do
IFS='|' read -r id repo tag created size <<< "$line"
short_id=${id:0:12}
LAST_IDS+=("$id")
LAST_LABELS+=("$repo|$tag|$short_id|$size|$created")
done
}
list_all_images() {
local -a lines
mapfile -t lines < <(docker images --no-trunc --format '{{.ID}}|{{.Repository}}|{{.Tag}}|{{.CreatedSince}}|{{.Size}}')
load_images_from_lines "${lines[@]}"
print_images_table
}
list_unused_images() {
local used_file
used_file=$(mktemp)
if docker ps -a -q >/dev/null 2>&1; then
docker ps -a -q | xargs -r docker inspect --format '{{.Image}}' | sort -u > "$used_file"
fi
local -a lines
mapfile -t lines < <(docker images --no-trunc --format '{{.ID}}|{{.Repository}}|{{.Tag}}|{{.CreatedSince}}|{{.Size}}')
LAST_IDS=()
LAST_LABELS=()
local line id repo tag created size short_id
for line in "${lines[@]}"; do
IFS='|' read -r id repo tag created size <<< "$line"
if [[ ! -s "$used_file" ]] || ! grep -Fxq "$id" "$used_file"; then
short_id=${id:0:12}
LAST_IDS+=("$id")
LAST_LABELS+=("$repo|$tag|$short_id|$size|$created")
fi
done
rm -f "$used_file"
print_images_table
}
confirm_action() {
local prompt="$1"
local answer
read -r -e -p "$prompt [y/N]: " answer
case "$answer" in
y|Y|yes|YES) return 0 ;;
*) return 1 ;;
esac
}
select_images_by_numbers() {
local -a numbers=()
local input="${1:-}"
SELECTED_IDS=()
SELECTED_LABELS=()
if [[ -z "$input" ]]; then
read -r -e -p "Nummern der Images (z.B. 1,3,5) oder Enter fuer Abbruch: " input
fi
if [[ -z "$input" ]]; then
echo "Abgebrochen."
return 1
fi
input=${input//,/ }
for token in $input; do
if [[ "$token" =~ ^[0-9]+$ ]]; then
numbers+=("$token")
fi
done
if [[ ${#numbers[@]} -eq 0 ]]; then
echo "Keine gueltigen Nummern angegeben."
return 1
fi
local idx
for idx in "${numbers[@]}"; do
if (( idx < 1 || idx > ${#LAST_IDS[@]} )); then
echo "Nummer $idx ist ungueltig."
return 1
fi
SELECTED_IDS+=("${LAST_IDS[$((idx - 1))]}")
SELECTED_LABELS+=("${LAST_LABELS[$((idx - 1))]}")
done
return 0
}
print_selected_images() {
if [[ ${#SELECTED_IDS[@]} -eq 0 ]]; then
return 0
fi
printf "%-4s %-30s %-20s %-15s %-12s %-20s\n" "Nr." "REPOSITORY" "TAG" "IMAGE ID" "SIZE" "CREATED"
printf "%-4s %-30s %-20s %-15s %-12s %-20s\n" "----" "------------------------------" "--------------------" "---------------" "------------" "--------------------"
local i
for i in "${!SELECTED_IDS[@]}"; do
IFS='|' read -r repo tag short_id size created <<< "${SELECTED_LABELS[$i]}"
printf "%-4s %-30s %-20s %-15s %-12s %-20s\n" "$((i + 1))" "$repo" "$tag" "$short_id" "$size" "$created"
done
}
remove_image_by_id() {
local id="$1"
local -a used
mapfile -t used < <(docker ps -a --filter "ancestor=$id" --format '{{.ID}}|{{.Names}}')
if [[ ${#used[@]} -gt 0 ]]; then
echo "Ueberspringe Image (in Benutzung durch Container): $id"
local entry
for entry in "${used[@]}"; do
echo "- ${entry#*|} (${entry%%|*})"
done
return
fi
local -a tags=()
mapfile -t tags < <(docker image inspect "$id" --format '{{range .RepoTags}}{{println .}}{{end}}')
if [[ ${#tags[@]} -eq 0 ]]; then
echo "Loesche Image: $id"
docker rmi "$id"
return
fi
local tag
local -a tag_list=()
for tag in "${tags[@]}"; do
if [[ "$tag" == "<none>:<none>" || -z "$tag" ]]; then
continue
fi
tag_list+=("$tag")
done
if [[ ${#tag_list[@]} -eq 0 ]]; then
echo "Loesche Image: $id"
docker rmi "$id"
return
fi
for tag in "${tag_list[@]}"; do
echo "Loesche Tag: $tag"
docker rmi "$tag"
done
if docker image inspect "$id" >/dev/null 2>&1; then
echo "Loesche Image: $id"
docker rmi "$id"
fi
}
delete_selected_images() {
local -A seen=()
local id
for id in "${SELECTED_IDS[@]}"; do
if [[ -n "${seen[$id]:-}" ]]; then
continue
fi
seen[$id]=1
remove_image_by_id "$id"
done
}
inspect_image_by_number() {
local num="${1:-}"
if [[ -z "$num" ]]; then
read -r -e -p "Nummer des Images: " num
fi
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
echo "Ungueltige Nummer."
return 1
fi
if (( num < 1 || num > ${#LAST_IDS[@]} )); then
echo "Nummer $num ist ungueltig."
return 1
fi
local id
id="${LAST_IDS[$((num - 1))]}"
docker image inspect "$id" --format \
$'ID: {{.Id}}\nRepoTags: {{.RepoTags}}\nCreated: {{.Created}}\nSize: {{.Size}}\nArchitecture: {{.Architecture}}\nOS: {{.Os}}\nLabels: {{json .Config.Labels}}'
echo
echo "Containers:"
local -a lines
mapfile -t lines < <(docker ps -a --filter "ancestor=$id" --format '{{.ID}}|{{.Names}}|{{.Status}}')
if [[ ${#lines[@]} -eq 0 ]]; then
echo "- keine"
return 0
fi
local line cid name status
for line in "${lines[@]}"; do
IFS='|' read -r cid name status <<< "$line"
echo "- $name ($cid) - $status"
done
}
purge_images() {
local mode="$1"
local force_yes="$2"
case "$mode" in
unused)
list_unused_images
if [[ ${#LAST_IDS[@]} -eq 0 ]]; then
return 0
fi
;;
*)
echo "Unbekannter Modus: $mode"
return 1
;;
esac
if [[ ${#LAST_IDS[@]} -eq 0 ]]; then
return 0
fi
if [[ "$force_yes" != "true" ]]; then
if ! confirm_action "Alle aufgelisteten Images loeschen?"; then
echo "Abgebrochen."
return 0
fi
fi
local -A seen=()
local id
for id in "${LAST_IDS[@]}"; do
if [[ -n "${seen[$id]:-}" ]]; then
continue
fi
seen[$id]=1
remove_image_by_id "$id"
done
}
print_help() {
echo "Docker Image Manager"
echo
echo "Verwendung:"
echo " bash ./docker-image-manager.v1.sh [BEFEHL] [OPTIONS]"
echo
echo "Befehle:"
echo " list - Alle Docker Images auflisten"
echo " unused-images - Images anzeigen, die von keinem Container genutzt werden"
echo " inspect - Details zu einem Image anzeigen (inkl. Containerliste)"
echo " delete - Einzelne Images nach Nummer loeschen"
echo " purge-unused - Alle ungenutzten Images loeschen"
echo " help, -h, --help - Diese Hilfe anzeigen"
echo
echo "Optionen:"
echo " --yes - Loeschen ohne Rueckfrage"
}
interactive_menu() {
while true; do
echo
echo "Docker Image Manager"
echo "1) Images anzeigen + Details"
echo "2) Ungenutzte Images anzeigen"
echo "3) Image loeschen (per Nummer)"
echo "4) Alle ungenutzten Images loeschen"
echo "0) Beenden"
read -r -e -p "Auswahl: " choice
case "$choice" in
1)
list_all_images
if [[ ${#LAST_IDS[@]} -eq 0 ]]; then
continue
fi
read -r -e -p "Nummer fuer Details (Enter fuer zurueck): " detail_num
if [[ -n "$detail_num" ]]; then
inspect_image_by_number "$detail_num"
fi
;;
2)
list_unused_images
;;
3)
list_all_images
if [[ ${#LAST_IDS[@]} -eq 0 ]]; then
continue
fi
if ! select_images_by_numbers ""; then
continue
fi
echo "Ausgewaehlte Images:"
print_selected_images
if confirm_action "Ausgewaehlte Images loeschen?"; then
delete_selected_images
else
echo "Abgebrochen."
fi
;;
4)
purge_images "unused" "false"
;;
0)
echo "Tschuess."
exit 0
;;
*)
echo "Ungueltige Auswahl."
;;
esac
done
}
main() {
require_docker
local cmd=""
local force_yes="false"
local -a extra_args=()
for arg in "$@"; do
case "$arg" in
--yes)
force_yes="true"
;;
-h|--help|help)
cmd="help"
;;
*)
if [[ -z "$cmd" ]]; then
cmd="$arg"
else
extra_args+=("$arg")
fi
;;
esac
done
if [[ -z "$cmd" ]]; then
interactive_menu
return 0
fi
case "$cmd" in
list)
list_all_images
;;
unused-images)
list_unused_images
;;
inspect)
list_all_images
if [[ ${#LAST_IDS[@]} -eq 0 ]]; then
return 0
fi
inspect_image_by_number "${extra_args[0]:-}"
;;
delete)
list_all_images
if [[ ${#LAST_IDS[@]} -eq 0 ]]; then
return 0
fi
if ! select_images_by_numbers "${extra_args[*]:-}"; then
return 1
fi
echo "Ausgewaehlte Images:"
print_selected_images
if [[ "$force_yes" != "true" ]]; then
if ! confirm_action "Ausgewaehlte Images loeschen?"; then
echo "Abgebrochen."
return 0
fi
fi
delete_selected_images
;;
purge-unused)
purge_images "unused" "$force_yes"
;;
help|*)
print_help
;;
esac
}
main "$@"