Sourcing User Profiles in Docker Images

Background

I needed a base Docker image with built-in support for toggling runtime settings – specifically, whether to use proxy settings or not. Building this into the base image presents a consistent UX for my teammates to use and an extensible framework for adding future toggles.

You can check out the repository on GitHub at neveroddoreven/alpine-bash-profile.

Implementation

Use Case

This is an example use case where I run the container and switch users. The full code is available on GitHub: neveroddoreven/alpine-bash-profile.

  1. When running a container with docker run, a startup script must run to initialize the environment
  2. The startup script should accept input in the form of flags or environment variables, passed in when calling docker run
  3. Regardless of user or session, automatically configure the environment based on the flags and options supplied when calling docker run

Example

> docker run -it --rm "neveroddoreven/alpine-bash-profile:latest" --proxies-on

Welcome appuser.

Setting known flags
export PROXIES_ON=1

appuser ~ $ sudo su -

Welcome root.

Setting known flags
export PROXIES_ON=1

root ~ $ exit
appuser ~ $ exit
>

Starting Dockerfile

I began by first adding some basic apk packages to my Dockerfile, then adding an appuser user, and finally setting the default shell to /bin/bash for all users.

FROM alpine:3.8

USER root

# Fetch the latest apk manifests
# Update existing packages
# Install bash and vim
# Cleanup after ourselves to keep this layer as small as possible
# Details: https://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management
RUN apk update \
    && apk upgrade \
    && apk add --no-cache bash vim sudo

# Add a group named "appusers"
#   -g, Assign this ID to the new group
# Details: https://busybox.net/BusyBox.html#addgroup
RUN addgroup -g 1000 appusers

# Add a user named "appuser"
#   -D, Do not assign a password
#   -u, Assign this ID to the new user
#   -s, Set this shell as the user's default login shell 
#   -h, Set this home path as the user's home path
#   -G, Add the new user to an existing group
# Details: https://busybox.net/BusyBox.html#adduser
RUN adduser -D -u 1000 -s /bin/bash -h /home/appuser -G appusers appuser

# Alpine Linux default shell for root is '/bin/ash'
# Change this to '/bin/bash' so that  '/etc/bashrc'
# can be loaded when entering the running container
RUN sed -i 's,/bin/ash,/bin/bash,g' /etc/passwd

# Add the 'appusers' group to the sudoers file
# No password required
RUN echo '%appusers ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers

USER appuser
WORKDIR /home/appuser

CMD [ "bash" ]

Adding Profile Sourcing Logic

# Add our custom welcome message script to profile.d.
# This is automatically sourced by /etc/profile when
# switching users.
COPY scripts/etc/profile.d/welcome.sh /etc/profile.d/welcome.sh

# The logic in this script can be whatever you want the container to do
# before running the entrypoint script. 
COPY scripts/etc/bashrc /etc/bashrc

# This will run immediately after the /etc/bashrc script
COPY scripts/usr/share/entrypoint.sh /usr/share/entrypoint.sh

# Must set this value for the bash shell to source 
# the '/etc/bashrc' file. See: https://stackoverflow.com/q/29021704.
ENV BASH_ENV /etc/bashrc

# Use this path to save any files generated when starting a container. 
# These files are accessible to root and appuser across sessions.
RUN mkdir -p /usr/share/entrypoint \
    && chown appuser:appusers /usr/share/entrypoint

USER appuser
WORKDIR /home/appuser

# This is the last necessary piece for loading the
# '/etc/bashrc' file, following the 'exec' syntax.
ENTRYPOINT [ "/usr/share/entrypoint.sh" ]

CMD [ "bash" ]

I currently use a derivative of this to support network proxies, and I have plans to extend this to support application feature flags.

Happy coding!