Last active
December 5, 2025 01:41
-
-
Save robertsinfosec/8dccc6ebaf629346a5701b3677eb2f91 to your computer and use it in GitHub Desktop.
Simple script to upgrade any Docker Compose services that use the `:latest` tag to update that service to the latest version of that container image. This could be run weekly, monthly or ad-hoc to make sure you are on the latest container image build. Consider putting this in `/usr/local/bin/` and `chmod +x compose-upgrade.sh` to mark it as exec…
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
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # compose-upgrade.sh | |
| # Usage: compose-upgrade.sh [--help] [--service-name NAME] [--service-path PATH] [--log-dir PATH] | |
| # | |
| # Runs: docker compose pull && docker compose up -d --remove-orphans | |
| # Prints progress with [+]/[-] markers, logs to file, and exits nonzero on failure. | |
| show_help() { | |
| cat <<'EOF' | |
| Usage: compose-upgrade.sh [OPTIONS] | |
| Options: | |
| --help Show this help and exit | |
| --service-name NAME Short name used in log filename (default: myservice) | |
| --service-path PATH Path to compose project dir containing docker-compose.yml (default: /path/to/your/composedir) | |
| --log-dir PATH Directory to write logs (default: /var/log) | |
| Examples: | |
| compose-upgrade.sh --service-name web --service-path /srv/web | |
| EOF | |
| } | |
| # Defaults (editable) | |
| SERVICE_NAME="tbd-service-change-me" | |
| SERVICE_PATH="/opt/path-to-docker-compose" | |
| LOG_DIR="/var/log" | |
| # Parse args (simple) | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| --help) show_help; exit 0 ;; | |
| --service-name) shift; SERVICE_NAME="${1:-}"; ;; | |
| --service-path) shift; SERVICE_PATH="${1:-}"; ;; | |
| --log-dir) shift; LOG_DIR="${1:-}"; ;; | |
| *) echo "Unknown arg: $1"; show_help; exit 2 ;; | |
| esac | |
| shift | |
| done | |
| LOG_FILE="${LOG_DIR%/}/compose-upgrade-${SERVICE_NAME}.log" | |
| DOCKER_BIN="$(command -v docker || true)" | |
| timestamp() { date -u +"%Y-%m-%d %H:%M:%S UTC"; } | |
| # Format elapsed seconds to ddd.HH:MM:ss | |
| format_elapsed() { | |
| local secs=$1 | |
| local days=$((secs/86400)); secs=$((secs%86400)) | |
| local hours=$((secs/3600)); secs=$((secs%3600)) | |
| local mins=$((secs/60)); local s=$((secs%60)) | |
| printf "%03d.%02d:%02d:%02d" "$days" "$hours" "$mins" "$s" | |
| } | |
| # Ensure log dir exists and writable | |
| mkdir -p "$LOG_DIR" | |
| if ! touch "$LOG_FILE" 2>/dev/null; then | |
| echo "[-] Error: cannot write to log file $LOG_FILE" >&2 | |
| exit 1 | |
| fi | |
| start_ts=$(date +%s) | |
| echo "=== $(timestamp) Starting compose upgrade for ${SERVICE_NAME} ===" >>"$LOG_FILE" | |
| echo "[*] Start: $(timestamp) for ${SERVICE_NAME}" | |
| fail() { | |
| local step="$1"; local msg="$2" | |
| echo "[-] Error: ${step} failed" | tee -a "$LOG_FILE" >&2 | |
| if [ -n "$msg" ]; then | |
| echo "[-] Message: $msg" | tee -a "$LOG_FILE" >&2 | |
| fi | |
| echo "=== $(timestamp) FAILED compose upgrade for ${SERVICE_NAME} ===" >>"$LOG_FILE" | |
| end_ts=$(date +%s) | |
| elapsed=$((end_ts-start_ts)) | |
| echo "=== Elapsed: $(format_elapsed "$elapsed") ===" >>"$LOG_FILE" | |
| echo "[-] Elapsed: $(format_elapsed "$elapsed")" | |
| exit 1 | |
| } | |
| succeed() { | |
| echo "[+] Success: $1" | |
| echo "=== $(timestamp) Completed compose upgrade for ${SERVICE_NAME} ===" >>"$LOG_FILE" | |
| end_ts=$(date +%s) | |
| elapsed=$((end_ts-start_ts)) | |
| echo "=== Elapsed: $(format_elapsed "$elapsed") ===" >>"$LOG_FILE" | |
| echo "[+] Elapsed: $(format_elapsed "$elapsed")" | |
| exit 0 | |
| } | |
| # Check docker binary | |
| if [ -z "$DOCKER_BIN" ]; then | |
| fail "Preflight" "docker binary not found in PATH" | |
| fi | |
| # Ensure SERVICE_PATH exists | |
| if [ ! -d "$SERVICE_PATH" ]; then | |
| fail "Preflight" "service path does not exist: $SERVICE_PATH" | |
| fi | |
| # Change to service dir | |
| echo "[*] Attempting to cd to ${SERVICE_PATH}..." | |
| if ! cd "$SERVICE_PATH" 2>&1 >>"$LOG_FILE"; then | |
| fail "cd" "Cannot cd to ${SERVICE_PATH}" | |
| fi | |
| echo "[+] Success: cd to ${SERVICE_PATH}" | |
| # Step 1: pull | |
| echo "[*] Attempting to docker compose pull..." | |
| if ! "$DOCKER_BIN" compose pull >>"$LOG_FILE" 2>&1; then | |
| # capture last 200 lines of log to show as message | |
| msg="$(tail -n 200 "$LOG_FILE")" | |
| fail "docker compose pull" "$msg" | |
| fi | |
| echo "[+] Success: docker compose pull completed" | |
| # Step 2: up -d --remove-orphans | |
| echo "[*] Attempting to docker compose up -d --remove-orphans..." | |
| if ! "$DOCKER_BIN" compose up -d --remove-orphans >>"$LOG_FILE" 2>&1; then | |
| msg="$(tail -n 200 "$LOG_FILE")" | |
| fail "docker compose up" "$msg" | |
| fi | |
| echo "[+] Success: docker compose up completed" | |
| # All done | |
| succeed "All steps completed successfully" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment