Skip to content

Instantly share code, notes, and snippets.

@anhtuank7c
Last active May 13, 2026 05:00
Show Gist options
  • Select an option

  • Save anhtuank7c/a8bb5d939920c1f62e6f434f2bb23da1 to your computer and use it in GitHub Desktop.

Select an option

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.
#!/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 "$@"
@anhtuank7c
Copy link
Copy Markdown
Author

anhtuank7c commented May 13, 2026

πŸ“¦ Install β€” make docker-autostart available everywhere

Quick install (one command)

System-wide:

sudo curl -fsSL https://gist.githubusercontent.com/anhtuank7c/a8bb5d939920c1f62e6f434f2bb23da1/raw/docker-autostart.sh -o /usr/local/bin/docker-autostart \                                                        
  && sudo chmod +x /usr/local/bin/docker-autostart                                                                                                                                                                 

User-only (no sudo, requires ~/.local/bin on $PATH):

mkdir -p ~/.local/bin \
  && curl -fsSL https://gist.githubusercontent.com/anhtuank7c/a8bb5d939920c1f62e6f434f2bb23da1/raw/docker-autostart.sh -o ~/.local/bin/docker-autostart \                                                          
  && chmod +x ~/.local/bin/docker-autostart                                                                                                                                                                        

Verify:

docker-autostart --help

πŸ”’ Safety note: The commands above download a file and mark it executable β€” they do not pipe untrusted code into your shell. If you'd prefer to read it before installing, see "Inspect first" below.

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.sh                                                                                                                                                                                             

Manual install (cloned or downloaded the file already)

sudo install -m 755 docker-autostart.sh /usr/local/bin/docker-autostart       # system-wide
install -m 755 docker-autostart.sh ~/.local/bin/docker-autostart              # user-only                                                                                                                          

Quick start

docker-autostart --status                       # see current policies
docker-autostart --dry-run                      # preview disabling autostart
docker-autostart                                # disable autostart on ALL containers
docker-autostart unless-stopped my-dev-db       # re-enable autostart on one container                                                                                                                             

Update

Re-run the same one-liner β€” curl -o and install both overwrite atomically.

⏱ GitHub caches Gist raw content for ~5 minutes. After you push a new revision, give it a few minutes (or pin the latest commit SHA in the URL β€” see below) before re-installing.

Uninstall

sudo rm /usr/local/bin/docker-autostart        # system-wide install
rm ~/.local/bin/docker-autostart               # user-only install                                                                                                                                                 

Pinning a specific version (optional)

Gist raw URLs serve the latest revision by default. To pin to an exact version, replace raw/ with raw/<commit-sha>/ in the URL β€” find the SHA under the Gist's Revisions tab.

Platform notes

Platform Notes
macOS (Intel) /usr/local/bin is on $PATH by default.
macOS (Apple Silicon) Homebrew lives at /opt/homebrew/bin; /usr/local/bin is still on $PATH and is preferable here because it keeps your scripts separate from Homebrew-managed binaries.
Linux Use /usr/local/bin, not /usr/bin. The latter is reserved for the distro package manager.
WSL2 Same as Linux. Do not install onto the Windows filesystem (/mnt/c/...) β€” the execute bit won't stick across the DrvFs boundary. Always copy to /usr/local/bin (which lives on the WSL filesystem).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment