Last active
May 13, 2026 05:00
-
-
Save anhtuank7c/a8bb5d939920c1f62e6f434f2bb23da1 to your computer and use it in GitHub Desktop.
Stop Docker containers from auto-starting when Docker Desktop launches. Bash script to bulk-set the restart policy (no / unless-stopped / always / on-failure) on all containers, with --status and --dry-run modes. macOS, Linux, WSL.
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 | |
| # docker-autostart.sh β bulk-manage Docker container restart policies (autostart on/off). | |
| # Author: Tuan Nguyen <https://github.com/anhtuank7c> | |
| # Co-author: Claude Code <https://claude.com/claude-code> | |
| # License: MIT. No warranty. Run --help for details. | |
| set -euo pipefail | |
| # MARK: - Constants | |
| # Single TAB byte, used as field separator for docker -f templates and column(1). | |
| TAB=$(printf '\t') | |
| # Container name grammar accepted by the docker CLI. | |
| CONTAINER_NAME_RE='^[A-Za-z0-9][A-Za-z0-9_.-]*$' | |
| # MARK: - Help | |
| # MARK: print_help | |
| print_help() { | |
| cat <<EOF | |
| $(basename "$0") β bulk-manage Docker container restart policies | |
| ABOUT | |
| Docker Desktop (and dockerd on Linux) automatically restarts containers | |
| on boot/login if their restart policy is anything other than "no". | |
| This script flips that policy for many containers at once, so you can: | |
| - stop containers from auto-starting when Docker Desktop opens | |
| - re-enable autostart for specific containers later | |
| - audit current policies across every container on the host | |
| USAGE | |
| $(basename "$0") [--dry-run] [POLICY] [CONTAINER...] | |
| $(basename "$0") --status [CONTAINER...] | |
| $(basename "$0") -h | --help | |
| ARGUMENTS | |
| POLICY Restart policy to apply. Optional. Default: no | |
| Must be one of: | |
| no never restart (disables autostart) | |
| on-failure restart only if container exits non-zero | |
| always always restart, even after manual stop | |
| unless-stopped restart unless you stopped it manually | |
| (the policy Docker Desktop uses by default) | |
| CONTAINER One or more container names or IDs to target. Optional. | |
| If omitted, the script targets ALL containers | |
| (running + stopped, as listed by 'docker ps -a'). | |
| OPTIONS | |
| -s, --status Read-only mode. Print a table of each container's | |
| NAME, STATE (running/exited/...), and current restart | |
| POLICY. POLICY argument is ignored in this mode. | |
| Accepts optional CONTAINER args to narrow the list. | |
| -n, --dry-run Preview only β no changes are made. Combine with ANY | |
| POLICY (no, on-failure, always, unless-stopped) and | |
| optional CONTAINER args to see exactly which containers | |
| would change and which already match the target. | |
| Recommended for a first run, especially when no | |
| CONTAINER args are given (which targets every | |
| container on the host). | |
| -h, --help Show this help and exit. | |
| EXAMPLES | |
| # Show current restart policy of every container (read-only) | |
| $(basename "$0") --status | |
| # Show current policy for two specific containers | |
| $(basename "$0") --status postgres redis | |
| # Preview disabling autostart on all containers (default POLICY = no) | |
| $(basename "$0") --dry-run | |
| # Preview enabling autostart on all containers | |
| $(basename "$0") --dry-run unless-stopped | |
| # Preview a change to a specific container only | |
| $(basename "$0") --dry-run always my-dev-db | |
| # Disable autostart for every container (most common use case) | |
| $(basename "$0") | |
| # Same as above, explicit | |
| $(basename "$0") no | |
| # Re-enable Docker Desktop's default autostart for everything | |
| $(basename "$0") unless-stopped | |
| # Re-enable autostart only for two specific containers | |
| $(basename "$0") unless-stopped postgres redis | |
| # Force-always-restart a single container by ID | |
| $(basename "$0") always 7f3c1b2a9d4e | |
| # Disable autostart for one container without touching the rest | |
| $(basename "$0") no my-dev-db | |
| OUTPUT | |
| Prints how many containers were updated, then a two-column list: | |
| <container-name> <resulting-policy> | |
| Use this to verify the change took effect. | |
| NOTES | |
| - Requires the docker CLI on PATH and a reachable Docker daemon. | |
| - The new policy is stored immediately; it governs the NEXT start of | |
| each container, which is what Docker Desktop's autostart checks. | |
| - Safe to re-run; the script is idempotent. | |
| TESTING STATUS | |
| - macOS (Docker Desktop, bash 3.2): TESTED β works. | |
| - Linux (dockerd or Docker Desktop): NOT yet tested β please verify | |
| before relying on it. | |
| - Windows WSL2 (Docker Desktop WSL integration): NOT yet tested. | |
| Likely caveats to watch for: | |
| * Line endings: if you edit the file in a Windows editor, save it | |
| with LF (not CRLF) or bash will fail with "bad interpreter". | |
| Convert with: dos2unix docker-autostart.sh | |
| * Docker CLI must be reachable from inside the WSL distro | |
| (Docker Desktop > Settings > Resources > WSL integration). | |
| * Execute permission may not stick on /mnt/c paths; either | |
| keep the script on the WSL filesystem (e.g. ~/) or invoke it | |
| explicitly: bash docker-autostart.sh | |
| AUTHOR | |
| Tuan Nguyen β https://github.com/anhtuank7c | |
| Co-authored with Claude Code β https://claude.com/claude-code | |
| License: MIT | |
| EOF | |
| } | |
| # MARK: - Validation | |
| # MARK: validate_policy | |
| validate_policy() { | |
| case "$1" in | |
| no|on-failure|always|unless-stopped) return 0 ;; | |
| *) | |
| printf 'Invalid policy: %q\n' "$1" >&2 | |
| printf 'Must be one of: no, on-failure, always, unless-stopped\n' >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| # MARK: validate_container_arg | |
| validate_container_arg() { | |
| local t="$1" | |
| if [[ -z "$t" ]]; then | |
| printf 'Refusing empty container argument.\n' >&2 | |
| exit 1 | |
| fi | |
| if [[ "$t" == -* ]]; then | |
| printf 'Refusing container argument that looks like an option: %q\n' "$t" >&2 | |
| exit 1 | |
| fi | |
| if [[ ! "$t" =~ $CONTAINER_NAME_RE ]]; then | |
| printf 'Refusing container argument with unexpected characters: %q\n' "$t" >&2 | |
| exit 1 | |
| fi | |
| } | |
| # MARK: build_targets | |
| # Populate the global `targets` array from positional args, or from | |
| # `docker ps -aq` when none are given. | |
| build_targets() { | |
| targets=() | |
| if [[ $# -gt 0 ]]; then | |
| for t in "$@"; do | |
| validate_container_arg "$t" | |
| targets+=("$t") | |
| done | |
| else | |
| while IFS= read -r line; do | |
| targets+=("$line") | |
| done < <(docker ps -aq) | |
| fi | |
| } | |
| # MARK: - Output helpers | |
| # MARK: print_table | |
| print_table() { | |
| { | |
| printf 'NAME\tSTATE\tPOLICY\n' | |
| docker inspect \ | |
| -f "{{.Name}}${TAB}{{.State.Status}}${TAB}{{.HostConfig.RestartPolicy.Name}}" \ | |
| -- "$@" \ | |
| | sed 's|^/||' | |
| } | column -t -s "$TAB" | |
| } | |
| # MARK: print_summary | |
| print_summary() { | |
| local total=$# | |
| local breakdown | |
| breakdown=$(docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' -- "$@" \ | |
| | sort | uniq -c \ | |
| | awk '{printf "%s%s=%d", (NR>1?", ":""), $2, $1}') | |
| printf '\n%d container(s): %s\n' "$total" "$breakdown" | |
| } | |
| # MARK: - Modes | |
| # MARK: do_status | |
| do_status() { | |
| print_table "$@" | |
| print_summary "$@" | |
| } | |
| # MARK: do_dry_run | |
| do_dry_run() { | |
| local policy="$1"; shift | |
| local total=$# | |
| local changing=() | |
| local unchanged=0 | |
| local name current | |
| while IFS="$TAB" read -r name current; do | |
| if [[ "$current" == "$policy" ]]; then | |
| unchanged=$((unchanged + 1)) | |
| else | |
| changing+=("${name#/}${TAB}${current}${TAB}β ${policy}") | |
| fi | |
| done < <(docker inspect -f "{{.Name}}${TAB}{{.HostConfig.RestartPolicy.Name}}" -- "$@") | |
| if [[ ${#changing[@]} -eq 0 ]]; then | |
| printf 'Nothing to do β all %d container(s) already at restart=%s.\n' \ | |
| "$total" "$policy" | |
| return 0 | |
| fi | |
| printf 'Plan: change %d of %d container(s) to restart=%s.\n\n' \ | |
| "${#changing[@]}" "$total" "$policy" | |
| { | |
| printf 'NAME\tCURRENT\tNEW\n' | |
| printf '%s\n' "${changing[@]}" | |
| } | column -t -s "$TAB" | |
| if [[ $unchanged -gt 0 ]]; then | |
| printf '\n(%d already at restart=%s β would be skipped.)\n' \ | |
| "$unchanged" "$policy" | |
| fi | |
| printf '\nDry run β no changes made. Re-run without --dry-run to apply.\n' | |
| } | |
| # MARK: do_apply | |
| do_apply() { | |
| local policy="$1"; shift | |
| printf 'Setting restart=%s on %d container(s)... ' "$policy" "$#" | |
| docker update --restart="$policy" -- "$@" >/dev/null | |
| printf 'done.\n\n' | |
| print_table "$@" | |
| print_summary "$@" | |
| } | |
| # MARK: - Main | |
| # MARK: main | |
| main() { | |
| local dry_run=0 | |
| local status_only=0 | |
| local show_help=0 | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --dry-run|-n) dry_run=1; shift ;; | |
| --status|-s) status_only=1; shift ;; | |
| -h|--help) show_help=1; shift ;; | |
| --) shift; break ;; | |
| *) break ;; | |
| esac | |
| done | |
| if [[ $show_help -eq 1 ]]; then | |
| print_help | |
| exit 0 | |
| fi | |
| local policy="" | |
| if [[ $status_only -eq 0 ]]; then | |
| policy="${1:-no}" | |
| shift || true | |
| validate_policy "$policy" | |
| fi | |
| build_targets "$@" | |
| if [[ ${#targets[@]} -eq 0 ]]; then | |
| echo "No containers found." | |
| exit 0 | |
| fi | |
| if [[ $status_only -eq 1 ]]; then | |
| do_status "${targets[@]}" | |
| elif [[ $dry_run -eq 1 ]]; then | |
| do_dry_run "$policy" "${targets[@]}" | |
| else | |
| do_apply "$policy" "${targets[@]}" | |
| fi | |
| } | |
| main "$@" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
π¦ Install β make
docker-autostartavailable everywhereQuick install (one command)
System-wide:
User-only (no sudo, requires
~/.local/binon$PATH):Verify:
Inspect first (safer)
curl -fsSL https://gist.githubusercontent.com/anhtuank7c/a8bb5d939920c1f62e6f434f2bb23da1/raw/docker-autostart.sh -o docker-autostart.sh less docker-autostart.sh # read it sudo install -m 755 docker-autostart.sh /usr/local/bin/docker-autostart rm docker-autostart.shManual install (cloned or downloaded the file already)
Quick start
Update
Re-run the same one-liner β
curl -oandinstallboth overwrite atomically.Uninstall
Pinning a specific version (optional)
Gist raw URLs serve the latest revision by default. To pin to an exact version, replace
raw/withraw/<commit-sha>/in the URL β find the SHA under the Gist's Revisions tab.Platform notes
/usr/local/binis on$PATHby default./opt/homebrew/bin;/usr/local/binis still on$PATHand is preferable here because it keeps your scripts separate from Homebrew-managed binaries./usr/local/bin, not/usr/bin. The latter is reserved for the distro package manager./mnt/c/...) β the execute bit won't stick across the DrvFs boundary. Always copy to/usr/local/bin(which lives on the WSL filesystem).