profile.d and entrypoint.d · Issue #19 · devcontainers/spec (original) (raw)
A persistent challenge in creating containers has been how to simply and easily add environment variables or source scripts as a part of various user shells (bash, zsh) and firing things on container startup - particularly if the thing that is starting requires root access.
Today we've dealt with this by:
- Having all of our scripts add content to /etc/bash.bashrc and /etc/zsh/zshrc
- Defaulting
userEnvProbe
tologinInteractiveShell
to ensure any tool gets these variables - Recommending containers run as root, but connect tools as non-root
- Creating entrypoint scripts that fall back on sudo if not running as root
- Providing an "entrypoint" property for the upcoming features capability (User contributable container features #8)
There are several challenges here.
- If a pure login shell is used via docker exec or userEnvProbe is updated, (1) will not work. Scripts can also be added to /etc/profile.d and /etc/zsh/zprofile (or zshenv) to solve this, but you then have to check to verify whether the script has already fired if you are doing something that shouldn't happen more than once or has overhead.
- For (3) and (4), sudo is not always present, and in some cases may be explicitly omitted for security reasons - particularly password-less. Running the container as root is also not a good idea on shared Linux hosts unless a VM is used to house the containers - like in Docker Desktop (which is also coming to Linux). There is "rootless" mode for container engines, but these also have limitations and end up causing problems for bind mounts since all files end up being owned by root. (Using a container volume does not have this limitation, however).
- For (5), currently the feature needs to be reference even if it is built into the container image. This can be quite confusing and easy to forget. In addition, adding an explicit entrypoint to a Dockerfile also requires
overrideCommand
to be set to false which has similar challenges - though the label metadata proposal could help here (Dev container metadata in image labels #18).
Proposal
Rather than all of this, we can have a well defined pattern when features are used. There could even be a simple feature that does nothing more than set up execution of this pattern. Instead of everything described above, we can introduce two well known folder locations:
- A
profile.d
folder (or a similar name). If a feature places a script in this location, it will be sourced by /etc/bash.bashrc, /etc/zsh/zshrc, /etc/profile.d, and /etc/zsh/zprofile. A single bootstrapper can then be sourced to ensure these scripts are executed with code similar to what you'd find in /etc/profile. However, it would check to see if the bootstrapper has already run to ensure it doesn't fire more than once like in the case of a login interactive shell.
if [ -z "${DEV_CONTAINER_PROFILE_BOOSTRAP_DONE}" ] && [ -d "/usr/local/etc/dev-containers/profile.d" ]; then
for script in /usr/local/etc/dev-containers/profile.d/*; do
if [ -r "$script" ]; then
. $script
fi
unset script
done
export DEV_CONTAINER_PROFILE_BOOSTRAP_DONE="true"
fi - An
entrypoint.d
folder where you can place a scripts that will automatically be executed as a part of the container startup. This eliminates the need for an explicit entrypoint property indevcontainer-features.json
. However, execution is a bit different thanprofile.d
. It should:- Be explicitly added to the container's entrypoint if not present already.
- Execute the scripts in alphabetical order so that, like
/etc/profile.d
, you can add numeric prefixes to indicate when it should happen. - The processor should then fire
exec
for any arguments passed into it so that the PID for the processor is replaced by the existing entrypoint commands.
Features then follow a pattern of placing contents in these folders rather than attempting to manually update configuration.
Alternatives
Part of the reason for entrypoint.d
is that most containers do not have systemd or another init system that supports process startup. (The tiny init system that the --init argument adds does not manage process startup). The challenge is systemd is heavy and requires more permissions to the host than is ideal. So while it is an option, its not something we could count on. An alternative would be to use something like supervisord. However, if the desire is just to fire something once at startup rather than keep a process running, its overkill as well. We can also easily document how to wire in supervisord using entrypoint.d
if not provide a dev container feature for it.