docker:reliable-compose-startup-after-reboot
Differences
This shows you the differences between two versions of the page.
| docker:reliable-compose-startup-after-reboot [2026/07/06 19:12] – created 127.0.0.1 | docker:reliable-compose-startup-after-reboot [Unknown date] (current) – external edit (Unknown date) 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Reliable Docker Compose Startup After a Reboot ====== | ||
| + | |||
| + | A common and confusing failure mode: a Docker Compose stack that runs perfectly when you start it by hand starts misbehaving **after the server reboots**. One container pins a CPU core, the machine runs hot and the fans spin up — even though the overall system load is near zero. This page explains **why it happens** and shows a clean, general way to prevent it. | ||
| + | |||
| + | ===== The trap: '' | ||
| + | |||
| + | Compose lets you declare start-up order and readiness gating: | ||
| + | |||
| + | <code yaml> | ||
| + | services: | ||
| + | aggregator: | ||
| + | depends_on: | ||
| + | backend: | ||
| + | condition: service_healthy | ||
| + | </ | ||
| + | |||
| + | This tells Compose: //"do not start '' | ||
| + | |||
| + | The catch: after a **host reboot**, your containers are usually brought back not by Compose, but by the **Docker daemon' | ||
| + | |||
| + | > **Key fact:** '' | ||
| + | |||
| + | ===== Why this causes a runaway (hot fans, pinned core) ===== | ||
| + | |||
| + | Many " | ||
| + | |||
| + | * holding a dead or half-open connection, | ||
| + | * retrying in a tight loop with no back-off, | ||
| + | * pinning a full CPU core at ~100%. | ||
| + | |||
| + | The result: the box is essentially idle (load near zero) yet **one core runs flat out**, temperatures climb and the fans ramp up — with no obvious " | ||
| + | |||
| + | **Analogy: | ||
| + | |||
| + | ===== The clean fix: let systemd own start-up ===== | ||
| + | |||
| + | The reliable pattern is to stop relying on the daemon' | ||
| + | |||
| + | === 1. Stop the daemon from race-starting containers === | ||
| + | |||
| + | Change the restart policy in your '' | ||
| + | |||
| + | <code yaml> | ||
| + | services: | ||
| + | backend: | ||
| + | restart: on-failure | ||
| + | aggregator: | ||
| + | restart: on-failure | ||
| + | </ | ||
| + | |||
| + | **Why:** the daemon auto-starts '' | ||
| + | |||
| + | === 2. Make systemd start the whole stack, in order === | ||
| + | |||
| + | Create ''/ | ||
| + | |||
| + | <code ini> | ||
| + | [Unit] | ||
| + | Description=My application stack (docker compose) | ||
| + | Requires=docker.service | ||
| + | After=docker.service network-online.target | ||
| + | Wants=network-online.target | ||
| + | |||
| + | [Service] | ||
| + | Type=oneshot | ||
| + | RemainAfterExit=yes | ||
| + | WorkingDirectory=/ | ||
| + | ExecStart=/ | ||
| + | ExecStop=/ | ||
| + | |||
| + | [Install] | ||
| + | WantedBy=multi-user.target | ||
| + | </ | ||
| + | |||
| + | Enable it once: | ||
| + | |||
| + | <code bash> | ||
| + | sudo systemctl daemon-reload | ||
| + | sudo systemctl enable --now mystack.service | ||
| + | </ | ||
| + | |||
| + | Now systemd is the **single** thing that starts the stack at boot, and '' | ||
| + | |||
| + | === 3. Handle cold-boot transients with a retry === | ||
| + | |||
| + | On a cold boot, a backend that needs the network (DNS, a remote API) may crash-loop for a few seconds until networking is fully up. '' | ||
| + | |||
| + | Make the unit retry, so the stack converges by itself: | ||
| + | |||
| + | <code ini> | ||
| + | [Unit] | ||
| + | # ... | ||
| + | StartLimitIntervalSec=900 | ||
| + | StartLimitBurst=6 | ||
| + | |||
| + | [Service] | ||
| + | # ... | ||
| + | Restart=on-failure | ||
| + | RestartSec=20 | ||
| + | </ | ||
| + | |||
| + | If the first attempt fails because a backend was still settling, systemd waits and re-runs '' | ||
| + | |||
| + | <code bash> | ||
| + | systemctl show mystack.service -p NRestarts --value | ||
| + | </ | ||
| + | |||
| + | ===== How to diagnose the runaway ===== | ||
| + | |||
| + | If a box runs hot after a reboot but overall load is low, look for a single pinned container: | ||
| + | |||
| + | <code bash> | ||
| + | # per-container CPU — the culprit sits near a full core | ||
| + | docker stats --no-stream | ||
| + | |||
| + | # confirm it is a spin, not real work, then read its logs | ||
| + | # for a tight retry / error loop (e.g. the same request repeating) | ||
| + | docker logs --tail 50 < | ||
| + | </ | ||
| + | |||
| + | A quick manual recovery (until the boot fix is in place) is simply to restart the affected container //once its dependencies are up//: | ||
| + | |||
| + | <code bash> | ||
| + | docker restart < | ||
| + | </ | ||
| + | |||
| + | ===== Optional: a runtime watchdog ===== | ||
| + | |||
| + | The steps above fix **boot** ordering. A separate, rarer problem is a long-running proxy that holds a **persistent session** to a backend and has no working auto-reconnect: | ||
| + | |||
| + | ===== Summary ===== | ||
| + | |||
| + | * '' | ||
| + | * Use '' | ||
| + | * Let a **systemd oneshot unit** run '' | ||
| + | * Add '' | ||
| + | * Result: the stack comes up in the correct order every time, and a single misbehaving service can no longer pin a core and roast your fans. | ||
