tl;dr: you’re missing the --login flag in your bash execution.


Hey,

yesterday I was trying to test some ansible roles I had written and to do so I was using a containerized version of Ubuntu that I tailored for doing the job - cirocosta/ubuntu.

The image does its job pretty well: it can run with systemd as the PID 1, it has SSH and I can mess around with systemd services, units .. etc etc as if I had a working “VM” (given the set of boundaries of how far we can simulate a VM). Anyway … I faced an issue: my ansible roles were setting some scripts at /etc/profile.d/ but they were not being executed! How dare they?

It turns out that something pretty simple was going on - the default shell (bash in that case) wasn’t being executed as “login shell”.

Looking at bash(1)’s man page:

--noprofile
	Do not read either the system-wide startup file 
	/etc/profile or any of the personal initialization 
	files ~/.bash_profile, ~/.bash_login, or ~/.profile. 
	By default, bash reads these files when it is invoked 
	as a login shell (see INVOCATION below).

Invocation

	A login shell is one whose first character of argument 
	zero is a -, or one started with the --login option.

i.e, when we run bash <blabla> by default, it won’t run as a login shell and it turns out that profile files are only read when running it as login. Does it? We can easily check that with strace:

# create a script to be sourced by bash on startup
echo "export FOO=bar" > /etc/profile.d/my-script.sh

# create a simplistic exec file
echo "echo hey!" > ./exec.sh

# execute it without '--login' 
strace -e open bash exec.sh 
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
...
open("/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
open("exec.sh", O_RDONLY)               = 3
hey!
+++ exited with 0 +++

# execute it with '--login' 
strace -e open bash --login exec.sh 
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
..
open("/dev/tty", O_RDWR|O_NONBLOCK)     = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
open("/etc/profile", O_RDONLY)          = 3
open("/etc/profile.d/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
open("/etc/profile.d/my-script.sh", O_RDONLY) = 3
open("exec.sh", O_RDONLY)               = 3
hey!
+++ exited with 0 +++

Closing thoughts

Do you need a full ubuntu container? Should you care about /etc/profile.d in a container? Probably not. Honestly, aside from testing ansible roles, I never needed.

It’s pretty cool how we can super easily verify a behavior making use of a combination of man to check the docs and strace to check the syscalls involved. Very handy.

If you have any questions or just noticed that I wrote something that’s totally wrong or that could be made better, reach out! I’m @cirowrc on Twitter. You can also subscribe to the newsletter to keep up with some stuff that I find very useful.

Cheers!