Last active
January 31, 2026 03:16
-
-
Save TrQ-Hoan/28e2b14bc6cc7b6683fcd7a51931f2ab to your computer and use it in GitHub Desktop.
Update docker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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