Build1 publisher2 min readPublished
A missing dollar sign made a redeploy hook stop a container that never existed
Two hours of debugging a git-push-to-deploy hook ended with set -x printing docker stop APP_NAME, the literal word. The writeup calls the cause inconsistent quoting; its own trace shows there was no expansion to quote.
The Engineer · Build desk

What happened
- A developer building mini-paas, a from-scratch git-push-to-deploy tool, wired a post-receive hook to check out pushed code, build a Docker image and swap the running container for the new one.
- Once the hook gained stop-remove-run logic for clean redeploys, every second push failed with Docker reporting that the container name /myapp was already in use.
- Adding set -x to the hook printed the executed lines, and docker stop and docker rm turned out to be running against the literal word APP_NAME while docker run used the value myapp.
- Rewriting the two lines as docker stop "$APP_NAME" and docker rm "$APP_NAME" restored the cycle, with the old container stopped and removed and the new one started without a conflict.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A line with no dollar sign passes a literal string, so reviews and checks hunting for quoting mistakes find nothing in it to flag; a post-substitution trace is the artifact that shows it.
- decision When a command works by hand and fails under automation, a single traced push distinguishes a wrong argument from a variable the automation never set, because the two print differently.
- exposure Until the fix, each failed redeploy left the earlier container running and the new image unstarted, so the deployed app kept serving the previous build.
Bash's xtrace prints each command after substitution. One traced push tells you something a fifth reading of the script cannot [5]. Several explanations fit the symptom, and each prints differently. A line referencing a variable that the hook's environment never set would have printed `docker stop` with an empty argument; a value containing a space, expanded unquoted, would have printed two arguments [2]. The push printed `+ docker stop APP_NAME` [6]. So the line passed the literal word `APP_NAME`, with no dollar sign in it and nothing to expand [3].
The post attributes the failure to inconsistent quoting, saying `$APP_NAME` had lost proper double-quote expansion on those two lines while the rest of the script used it correctly [8]. The trace it publishes shows something narrower. In the fix, each of the two lines gains three characters, a dollar sign and a pair of double quotes, six across the patch [9][1]. Only the dollar sign changed what the commands targeted: the value was `myapp`, the same single word `docker run` passed to `--name` two lines later in the same trace, and a word with no whitespace does not split when expanded unquoted [6][4].
The conflict was the part that showed up in the output. Every second push printed `Conflict. The container name "/myapp" is already in use` [2]. The two commands ahead of it were the ones at fault. They ran against a name that had never been created, and the script continued to `docker run` [7]. With the old container still present and the new one never created, each failed push left the previous build in place [5].
The manual test is what made the search expensive. `docker stop myapp` and `docker rm myapp` typed at the shell worked instantly, no errors and no delay [4]. That result cleared Docker. What it tested was Docker, not the hook's invocation. About two hours went into the wrong layer, on the assumption that the problem had to be Docker, WSL or something else environmental [10].
"Reading code shows you what you intended. Tracing execution shows you what actually happened," the author wrote [11]. The stated conclusion is that `set -x` now comes first, as the opening diagnostic step instead of a last resort after manual testing is exhausted [12]. The same account reports a second stumble on the way: pushing to `master` while following a workflow that assumed `main` was the default branch [13].
What to watch
- Whether the hook gains a shell option that aborts when a stop or remove step fails, instead of continuing on to docker run.
- Whether set -x stays on permanently in the hook or moves behind a debug flag, and what the pusher sees in the remote output either way.
- Whether the same container name appears elsewhere in the script on lines the single traced push never exercised.