Initialer Release (v1.0.0)
This commit is contained in:
480
docker-image-manager.v1.sh
Normal file
480
docker-image-manager.v1.sh
Normal 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 "$@"
|
||||
Reference in New Issue
Block a user