The prompt that asks the kernel
Every Arch template that vps-lxc built got a bashrc whose final prompt stanza answers the question “am I in a container, and which one?” by asking the kernel instead of trusting the environment:
# if we are the host in a container, let's make sure we don't forget that
is_cgroup="$(expr "$(cat /proc/self/cgroup 2>&-)" : '.*/\([-A-Za-z0-9_]*\)')"
is_tty="$(tty | grep tty)"
if [ -n "${is_cgroup}" -a -n "${is_tty}" ]; then
if ${use_color}; then
PS1="\[\033[01;34m\]VPS[\[\033[37m\]${is_cgroup}\[\033[34m\]]\[\033[00m\] ${PS1}"
else
PS1="VPS[${is_cgroup}] ${PS1}"
fi
fiHow it works: LXC placed a container’s processes in a cgroup named after the container, and /proc/self/cgroup reports the cgroup path of the reading process. The expr match captures the last path component—the container name. On the host the path ends at /, the capture comes back empty, and the prompt stays untouched. Inside a container the shell renders as VPS[name] user@host ..., and the comment in the file states the stakes plainly: “let’s make sure we don’t forget that.”
Why /proc/self beats an environment variable: an env var is set once by whatever entered the container and copied at every fork after that—it can be missing, stale, or inherited across a boundary it should not have crossed. /proc/self/cgroup is evaluated fresh by each shell about itself, so the answer is the kernel’s current opinion, not a souvenir from the process tree.
The second gate, tty | grep tty, tries to hold the banner to console TTYs. It distinguishes a /dev/tty1-style console, where a container’s gettys sit and vps-lxc enter lands via lxc-console, from a /dev/pts/0 login, which does not contain tty. But it is not a real TTY test. With no terminal attached, tty prints not a tty; grep matches that diagnostic, leaves is_tty nonempty, and the gate succeeds. The historical test therefore marks the intended console entry path but can also fire in a noninteractive shell.
The gotcha is that the extraction reads differently today. On cgroup v2 the file collapses to a single 0::/path line, which the expr would still parse. A runtime may also put the container in a private cgroup namespace; when it does, /proc/self/cgroup can read simply 0::/ inside while the host-side view retains the full path. That mode is configurable, not universal, but it removes the host identity this trick harvested. A modern equivalent therefore needs an explicit runtime-provided identity channel. An environment variable is one option; a file, hostname, configuration value, or runtime API can carry the same fact without pretending /proc/self/cgroup still does.