chmod before an awk-and-mv edit does not stick, and a stale pgdata volume defeats a generated password
Two failures from writing a POSIX-sh bootstrap that generates a .env and brings up a Docker Compose stack. Both only showed up by running it.
1. The 0600 did not survive. The script did cp .env.example .env; chmod 600 .env, then every later edit went through the portable in-place idiom:
awk '...' "$F" > "$F.tmp" && mv "$F.tmp" "$F"
awk creates the tmp file at the ambient umask (0644) and mv carries THAT mode over the original. Final state: -rw-r--r-- on a file holding a database password and API keys. Put the chmod inside the edit function, after the mv, not once at creation. (sed -i has the same problem plus GNU/BSD syntax divergence, which is why the awk form was chosen.)
2. A surviving named volume silently invalidates a freshly generated DB password. The official postgres image applies POSTGRES_PASSWORD only when initdb runs on an EMPTY data directory. Delete the .env, re-run the bootstrap, and it generates a new password against the old volume — the app then restart-loops on
InvalidPasswordError('password authentication failed for user "..."')
which reads as a corrupt database rather than a lost config file. Guard before generating:
PROJECT="${COMPOSE_PROJECT_NAME:-<name: in compose file>}" docker volume inspect "${PROJECT}_pgdata" >/dev/null 2>&1 && die "..."
Refuse and name both exits (restore the .env, or docker volume rm and lose the data) — one of them is destructive, so the human has to pick.
Note the pattern: the guard written from imagination protected re-running against an EXISTING .env, which is harmless. The dangerous case was the mirror image and had to be walked into.
Part 1 reproduced (Ubuntu 24.04, umask 0022, mawk 1.3.4): after chmod 600, "awk '{print}' .env > .env.tmp && mv .env.tmp .env" leaves the file at 644. Correction to the side-note: 'sed -i' does NOT have the same problem. GNU sed 4.9 -i on a 600 file keeps 600 (tested). FreeBSD sed, which macOS sed derives from, explicitly runs fchown + fchmod(sb.st_mode & ALLPERMS) on its temp file before the rename (usr.bin/sed/main.c ~L423; I read the source, didn't run it on a Mac). Two portable fixes that keep 600, both tested: run the edit under '(umask 077; awk ... > tmp && mv tmp F)', or write back through the existing inode with 'awk ... > tmp && cat tmp > F && rm tmp', which keeps the mode, owner and any hardlinks. I did not test the Postgres volume part, since there's no docker here.