Skip to main content

max / alloy

Declare the postgres /var, so a DB=postgres16 image builds bootc container lint refused every DB=postgres16 mint, which is why no fw13 image had ever been built. postgresql16-server's %post leaves content in /var, and /var is per-machine state on a bootc system: an upgrade re-syncs /usr and leaves /var alone, so anything arriving there in a later image never reaches a running machine. Two problems in one warning. The three directories only needed declaring, and the modes are read off the failed build's last layer rather than chosen. The .bash_profile could not be declared at all: tmpfiles.d has no type describing an existing file's contents. It moves to /usr, where image content belongs, and a C line copies it back on first boot -- kept rather than deleted because it exports PGDATA, and su - postgres is how a scratch cluster gets restored into. The gate keys on DB rather than PROFILE: fw13 is a client with a database and astra is a server with one, so the two dials do not nest.
Author: Max Johnson <me@maxj.phd> · 2026-09-04 06:56 UTC
Signed with PGP, not checked
Commit: dce2de0de34a464059fab16539d2b0142788d1f9
Parent: da0e7a0
2 files changed, +84 insertions, -1 deletion
M Containerfile +46 -1
@@ -2120,7 +2120,21 @@
2120 2120 command -v postgres >/dev/null \
2121 2121 || { echo "DB=postgres16 was asked for and postgres is not in the image" >&2; exit 1; }; \
2122 2122 psql --version | grep -q " 16\." \
2123 - || { echo "DB=postgres16 installed $(psql --version), which is not the major production runs" >&2; exit 1; } \
2123 + || { echo "DB=postgres16 installed $(psql --version), which is not the major production runs" >&2; exit 1; }; \
2124 + # The RPM's %post leaves a regular file in /var, and /var is per-machine
2125 + # state on a bootc system: `bootc container lint` fails the build on a
2126 + # non-directory there, because tmpfiles.d has no type that describes an
2127 + # existing file's contents and so nothing can declare it. Moved under
2128 + # /usr, where image content belongs; 50-alloy-var-postgres.conf copies it
2129 + # back on first boot. Not deleted, because it exports PGDATA and `su -
2130 + # postgres` is how a scratch cluster gets restored into.
2131 + install -D -m 0644 -o root -g root \
2132 + /var/lib/pgsql/.bash_profile /usr/share/alloy/pgsql/bash_profile; \
2133 + rm -f /var/lib/pgsql/.bash_profile; \
2134 + grep -q '^export PGDATA$' /usr/share/alloy/pgsql/bash_profile \
2135 + || { echo "postgres .bash_profile no longer exports PGDATA; the tmpfiles C line would restore a file that does nothing" >&2; exit 1; }; \
2136 + [ ! -e /var/lib/pgsql/.bash_profile ] \
2137 + || { echo "postgres .bash_profile is still in /var; bootc lint will refuse the image" >&2; exit 1; } \
2124 2138 ;; \
2125 2139 none) \
2126 2140 ! command -v psql >/dev/null \
@@ -2573,6 +2587,37 @@
2573 2587 fi; \
2574 2588 echo "var: tmpfiles declarations present and accepted for profile=$PROFILE"
2575 2589
2590 + # The database's /var, on the same rule as the client half above.
2591 + #
2592 + # Separate from that block because it keys on $DB rather than $PROFILE: fw13 is
2593 + # a client with a database and astra is a server with one, so the two dials do
2594 + # not nest. Same shape otherwise — the file is deleted rather than shipped inert
2595 + # on DB=none, because every line in it names the `postgres` user and
2596 + # systemd-tmpfiles fails a line whose user does not resolve.
2597 + #
2598 + # The --dry-run --create is the check that matters, for the reason the block
2599 + # above gives: a typo in a tmpfiles line is accepted everywhere except the boot
2600 + # it silently does nothing on. Here it also proves the `postgres` user actually
2601 + # resolves in this image, which is the half that would otherwise only fail on a
2602 + # machine.
2603 + RUN set -eux; \
2604 + pg=/usr/lib/tmpfiles.d/50-alloy-var-postgres.conf; \
2605 + if [ "$DB" = none ]; then \
2606 + rm -f "$pg"; \
2607 + [ ! -e "$pg" ] \
2608 + || { echo "DB=none still carries the postgres /var declarations, whose user this image has no packages to create" >&2; exit 1; }; \
2609 + echo "var: no database declarations on DB=none"; \
2610 + else \
2611 + [ -f "$pg" ] || { echo "$pg did not land; postgres /var would be undeclared and bootc lint refuses that" >&2; exit 1; }; \
2612 + getent passwd postgres >/dev/null \
2613 + || { echo "$pg names the postgres user and this image has no such account" >&2; exit 1; }; \
2614 + systemd-tmpfiles --dry-run --create "$pg" >/dev/null \
2615 + || { echo "systemd-tmpfiles rejects $pg" >&2; exit 1; }; \
2616 + test -f /usr/share/alloy/pgsql/bash_profile \
2617 + || { echo "$pg copies a bash_profile out of /usr and it is not there" >&2; exit 1; }; \
2618 + echo "var: postgres declarations present and accepted for db=$DB"; \
2619 + fi
2620 +
2576 2621 # =====================================================================
2577 2622 # The Firefox configuration is what pays for picking Firefox.
2578 2623 # =====================================================================
@@ -1,0 +1,38 @@
1 + # The postgres server's /var, declared so it survives an upgrade.
2 + #
3 + # Only shipped when DB=postgres16. `postgresql16-server`'s %post creates
4 + # /var/lib/pgsql and two directories under it on the build host, which makes
5 + # them image content in /var: `bootc install` copies that into the stateroot, so
6 + # a fresh install has them, and an upgrade re-syncs /usr and leaves /var alone,
7 + # so a machine already running never grows them. `bootc container lint` fails
8 + # the build on undeclared /var content (`var-tmpfiles`) for exactly that reason,
9 + # and it is what caught this: no fw13 image had ever built.
10 + #
11 + # Deleted on DB=none rather than shipped and inert, the same way
12 + # 50-alloy-var-client.conf is deleted on `server`: every line here names the
13 + # `postgres` user, which arrives with the packages that DB=none does not
14 + # install, and systemd-tmpfiles fails a line whose user does not resolve.
15 + #
16 + # Modes and owners are read off the built image rather than chosen: 0700
17 + # postgres postgres on all three, which is what the RPM's %post sets and what
18 + # initdb refuses to run without.
19 + #
20 + # THE .bash_profile IS NOT A tmpfiles LINE BY ACCIDENT. The RPM also ships
21 + # /var/lib/pgsql/.bash_profile, a regular file, and tmpfiles.d has no type that
22 + # describes an existing file's contents — bootc reports those separately as
23 + # "non-directory/non-symlink files in /var" and there is nothing to declare that
24 + # fixes it. So the Containerfile moves it under /usr, where image content
25 + # belongs, and `C` copies it back on first boot. `C` copies only when the
26 + # destination is absent, so a machine whose postgres user edited the file keeps
27 + # the edit.
28 + #
29 + # It matters rather than being tidiness: the file exports PGDATA, and `su -
30 + # postgres` on a build host is how a scratch cluster gets restored into. Dropping
31 + # it would leave initdb and pg_ctl with no data directory and no error that names
32 + # the cause.
33 + #
34 + #Type Path Mode User Group Age Argument
35 + d /var/lib/pgsql 0700 postgres postgres - -
36 + d /var/lib/pgsql/backups 0700 postgres postgres - -
37 + d /var/lib/pgsql/data 0700 postgres postgres - -
38 + C /var/lib/pgsql/.bash_profile 0644 postgres postgres - /usr/share/alloy/pgsql/bash_profile