Skip to content

Instantly share code, notes, and snippets.

@TrQ-Hoan
Last active January 31, 2026 03:16
Show Gist options
  • Select an option

  • Save TrQ-Hoan/28e2b14bc6cc7b6683fcd7a51931f2ab to your computer and use it in GitHub Desktop.

Select an option

Save TrQ-Hoan/28e2b14bc6cc7b6683fcd7a51931f2ab to your computer and use it in GitHub Desktop.
Update docker
#!/bin/bash
set -euo pipefail
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default values
DRY_RUN=false
AUTO_CLEANUP=false
COMPOSE_CMD=""
COMPOSE_FILE=""
# Usage information
usage() {
cat << EOF
Usage: $(basename "$0") [OPTIONS] [SERVICES...]
Update Docker Compose services by pulling images and recreating containers.
OPTIONS:
-h, --help Show this help message
-c, --check Check which images are outdated without pulling
-d, --dry-run Show what would be updated (alias for --check)
-y, --yes Auto-accept cleanup prompt (cleanup unused images)
-n, --no-cleanup Skip cleanup prompt (don't cleanup)
EXAMPLES:
$(basename "$0") # Update all services
$(basename "$0") web db # Update specific services
$(basename "$0") -c # Check for outdated images
$(basename "$0") -y web # Update web service and auto-cleanup
EOF
}
# Print colored message
print_msg() {
local color=$1
shift
echo -e "${color}$*${NC}"
}
# Detect compose command
detect_compose_command() {
# Try docker compose (v2) first
if docker compose version &>/dev/null; then
COMPOSE_CMD="docker compose"
return 0
fi
# Try docker-compose (v1)
if command -v docker-compose &>/dev/null; then
COMPOSE_CMD="docker-compose"
return 0
fi
print_msg "$RED" "Error: Neither 'docker compose' nor 'docker-compose' found."
exit 1
}
# Detect compose file
detect_compose_file() {
local files=("compose.yaml" "compose.yml" "docker-compose.yaml" "docker-compose.yml")
for file in "${files[@]}"; do
if [ -f "$file" ]; then
COMPOSE_FILE="$file"
print_msg "$GREEN" "Found compose file: $COMPOSE_FILE"
return 0
fi
done
print_msg "$RED" "Error: No compose file found in the current directory."
print_msg "$YELLOW" "Looked for: ${files[*]}"
exit 1
}
# Get image ID from local image
get_local_image_id() {
local image=$1
docker image inspect "$image" --format='{{index .Id}}' 2>/dev/null | cut -d: -f2 || echo ""
}
# Check if local image exists in remote manifest
check_remote_has_local_image() {
local image=$1
local local_image_id=$2
local tag="${image##*:}"
# If no tag specified, use latest
if [[ "$image" != *":"* ]]; then
tag="latest"
image="${image}:${tag}"
fi
# Try to get manifest with verbose output
local manifest_output
manifest_output=$(docker manifest inspect "$image" --verbose 2>/dev/null || echo "")
if [ -z "$manifest_output" ]; then
echo "error"
return 1
fi
# Check if local image ID exists in manifest
if echo "$manifest_output" | grep -q "$local_image_id"; then
echo "up-to-date"
return 0
else
echo "outdated"
return 0
fi
}
# Check if image is outdated
check_image_status() {
local image=$1
local service_name=$2
print_msg "$BLUE" " Checking: $image"
# Get local image ID
local local_image_id
local_image_id=$(get_local_image_id "$image")
if [ -z "$local_image_id" ]; then
print_msg "$YELLOW" " ⚠ Image not found locally"
return 2
fi
# Check if image exists in remote manifest
local status
status=$(check_remote_has_local_image "$image" "$local_image_id")
if [ "$status" = "error" ]; then
print_msg "$YELLOW" " ⚠ Cannot fetch remote manifest"
return 1
fi
# Compare status
if [ "$status" = "up-to-date" ]; then
print_msg "$GREEN" " ✓ Up to date"
print_msg "$YELLOW" " Local ID: ${local_image_id:0:12}"
return 0
else
print_msg "$RED" " ✗ Update available"
print_msg "$YELLOW" " Local ID: ${local_image_id:0:12}"
return 3
fi
}
# Get images from compose file
get_compose_images() {
local services=("$@")
local config_output
config_output=$($COMPOSE_CMD config)
if [ ${#services[@]} -gt 0 ]; then
# Check specific services
for service in "${services[@]}"; do
local image
image=$(echo "$config_output" | awk -v service="$service" '
$0 ~ "^ " service ":" {found=1; next}
found && /^ [a-zA-Z]/ {found=0}
found && /image:/ {print $2; exit}
')
if [ -n "$image" ]; then
echo "$service|$image"
fi
done
else
# Get all services
echo "$config_output" | awk '
/^ [a-zA-Z][a-zA-Z0-9_-]*:/ {
service = $1
gsub(/:/, "", service)
in_service = 1
next
}
in_service && /^ [a-zA-Z]/ && !/^ / {
in_service = 0
}
in_service && /^ image:/ {
gsub(/^ image: /, "")
print service "|" $0
in_service = 0
}
'
fi
}
# Check for outdated images
check_outdated_images() {
local services=("$@")
print_msg "$YELLOW" "=== Checking for outdated images ==="
local has_updates=false
local services_to_update=()
local images_data
# Store all images data in a variable
images_data=$(get_compose_images "${services[@]}")
# Check if we got any images
if [ -z "$images_data" ]; then
print_msg "$RED" "No images found in compose file"
return 1
fi
# Process each line
while IFS='|' read -r service image; do
if [ -z "$service" ] || [ -z "$image" ]; then
continue
fi
print_msg "$BLUE" "Service: $service"
# Use || true to prevent early exit on non-zero status
check_image_status "$image" "$service" || true
local status=$?
if [ $status -eq 3 ] || [ $status -eq 2 ]; then
has_updates=true
services_to_update+=("$service")
fi
echo
done <<< "$images_data"
if [ "$has_updates" = true ]; then
print_msg "$YELLOW" "Services with updates available: ${services_to_update[*]}"
print_msg "$YELLOW" "Run without -c/--check flag to update."
return 1
else
print_msg "$GREEN" "All images are up to date!"
return 0
fi
}
# Pull images
pull_images() {
local services=("$@")
if [ ${#services[@]} -gt 0 ]; then
print_msg "$YELLOW" "Pulling Docker images for services: ${services[*]}"
$COMPOSE_CMD pull "${services[@]}"
else
print_msg "$YELLOW" "Pulling all Docker images..."
$COMPOSE_CMD pull
fi
}
# Start services
start_services() {
local services=("$@")
if [ ${#services[@]} -gt 0 ]; then
print_msg "$YELLOW" "Starting services: ${services[*]}"
$COMPOSE_CMD up -d --force-recreate "${services[@]}"
else
print_msg "$YELLOW" "Starting all services..."
$COMPOSE_CMD up -d --force-recreate
fi
}
# Cleanup unused images
cleanup_images() {
if [ "$AUTO_CLEANUP" = true ]; then
print_msg "$YELLOW" "Cleaning up unused Docker images..."
docker image prune -f
elif [ "$AUTO_CLEANUP" = false ]; then
print_msg "$YELLOW" "Skipping cleanup."
else
read -p "Do you want to clean up unused Docker images? (y/n): " answer
if [[ "$answer" =~ ^[Yy]$ ]]; then
print_msg "$YELLOW" "Cleaning up unused Docker images..."
docker image prune -f
else
print_msg "$YELLOW" "Skipping cleanup."
fi
fi
}
# Main function
main() {
# Check for help flag first
for arg in "$@"; do
if [ "$arg" = "-h" ] || [ "$arg" = "--help" ]; then
usage
exit 0
fi
done
# Parse arguments
local services=()
while [[ $# -gt 0 ]]; do
case $1 in
-c|--check|-d|--dry-run)
DRY_RUN=true
shift
;;
-y|--yes)
AUTO_CLEANUP=true
shift
;;
-n|--no-cleanup)
AUTO_CLEANUP=false
shift
;;
-*)
print_msg "$RED" "Error: Unknown option: $1"
usage
exit 1
;;
*)
services+=("$1")
shift
;;
esac
done
print_msg "$GREEN" "=== Docker Compose Update Script ==="
# Detect compose command and file
detect_compose_command
detect_compose_file
print_msg "$GREEN" "Using: $COMPOSE_CMD"
echo
# Check mode
if [ "$DRY_RUN" = true ]; then
check_outdated_images "${services[@]}"
exit $?
fi
# Pull and start
pull_images "${services[@]}"
start_services "${services[@]}"
cleanup_images
print_msg "$GREEN" "Update completed successfully!"
}
# Run main function
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment