49

I have a docker file as below. launch.sh is the entry point in this docker image.

FROM ubuntu:16.04
USER root

RUN apt-get update && apt-get install -y \
        curl \
        vim \
        net-tools \
        git \
        iputils-ping \
        wget

RUN apt-get install -y python
RUN apt-get update && apt-get install -y gcc g++ make libgflags-dev libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev

RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash

ENV NVM_DIR /root/.nvm
RUN . $NVM_DIR/nvm.sh && \
  nvm install 7.9.0 && npm install -g npm@5.6.0

ADD ./Docker/launch.sh /workspace/

CMD ["/bin/sh", "/workspace/launch.sh"]

The content of launch.sh is:

#!/bin/bash

cd /workspace/demo
npm install
node index.js

when I run the docker container: docker run IMAGE_NAME, I got this error:

npm: not found
node: not found

The node in this image is managed by nvm which has been installed and its script has been set on /root/.bashrc file. But I don't know why it can't find the nodejs commands. But if I run the container by docker run -it IMAGE_NAME bash, then manually run workspace/launch.sh command, everything works fine. It seems the ~/.bashrc is not executed when run the image. How can I let the container source .bashrc?

The content of /root/.bashrc is:

# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

# don't put duplicate lines in the history. See bash(1) for more options
# ... or force ignoredups and ignorespace
HISTCONTROL=ignoredups:ignorespace

# append to the history file, don't overwrite it
shopt -s histappend

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000

# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize

# make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"

# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then
    debian_chroot=$(cat /etc/debian_chroot)
fi

# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
    xterm-color) color_prompt=yes;;
esac

# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
#force_color_prompt=yes

if [ -n "$force_color_prompt" ]; then
    if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
    # We have color support; assume it's compliant with Ecma-48
    # (ISO/IEC-6429). (Lack of such support is extremely rare, and such
    # a case would tend to support setf rather than setaf.)
    color_prompt=yes
    else
    color_prompt=
    fi
fi

if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt

# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
    PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
    ;;
*)
    ;;
esac

# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
    alias ls='ls --color=auto'
    #alias dir='dir --color=auto'
    #alias vdir='vdir --color=auto'

    alias grep='grep --color=auto'
    alias fgrep='fgrep --color=auto'
    alias egrep='egrep --color=auto'
fi

# some more ls aliases
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'

# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi

# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
#if [ -f /etc/bash_completion ] && ! shopt -oq posix; then
#    . /etc/bash_completion
#fi

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
Szymon Maszke
  • 22,747
  • 4
  • 43
  • 83
Joey Yi Zhao
  • 37,514
  • 71
  • 268
  • 523
  • 2
    Add . /root/.bashrc OR source /root/.bashrc in you script. – Slawomir Dziuba Mar 17 '19 at 10:57
  • Have you looked at the contents of your .bashrc file? Please include the entire contents of this file in your question. – BMitch Mar 17 '19 at 12:42
  • I get "source: not found" error when add `source /root/.bashrc` in my script. There will be many errors about command not found if I add `. /root/.bashrc` on the script. – Joey Yi Zhao Mar 17 '19 at 23:49

4 Answers4

26

Each command runs a separate sub-shell, so the environment variables are not preserved and .bashrc is not sourced (see this answer).

You have to source your script manually in the same process where you run your command so it would be:

CMD source /root/.bashrc && /workspace/launch.sh

provided your launch.sh is an executable.

As per documentation exec form you are using does not invoke a command shell, so it won't work with your .bashrc.

Edit:

BASH wasn't your default shell so

CMD /bin/bash -c "source /root/.bashrc && /workspace/launch.sh"

was needed in order to run your script. If you want yo set your shell as BASH by default, you can use SHELL instruction as described in documentation, e.g.:

SHELL ["/bin/bash", "-c"]
Szymon Maszke
  • 22,747
  • 4
  • 43
  • 83
22

None of the existing answers accurately answer the title question: Why ~/.bashrc is not executed when run docker container?

There are two things to be aware of:

Use login shell

According to the bash man page:

When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable. The --noprofile option may be used when the shell is started to inhibit this behavior.

Therefore, in order to have .profile/.bashrc read automatically upon invocation of bash, it is necessary to invoke bash with the --login or -l option.

You can do this in a couple ways:

1. Set the shell to include -l option. For example,

SHELL ["/bin/bash", "-l", "-c"]

2. Invoke -l for specific commands using the exec form of RUN:

CMD ["/bin/bash", "-l", "-c", "/workspace/launch.sh"]

Note top of .bashrc

From the man page above, we know the order in which profile files are searched and loaded. If you look at /root/.profile you may see something like this:

# ~/.profile: executed by Bourne-compatible login shells.

if [ "$BASH" ]; then
  if [ -f ~/.bashrc ]; then
    . ~/.bashrc
  fi
fi

mesg n 2> /dev/null || true

This is how ~/.bashrc gets source for a bash shell. Therefore, we can expect ~/.bashrc to be sourced when the bash shell is used.

However, look carefully near the top of your .bashrc file:

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

This means that effectively the remaining contents of .bashrc are ignored except for interactive shells.

One answer suggests using the -i option of bash to invoke an interactive shell. This does work because the environment variable PS1 is set for interactive shells, and therefore .bashrc continues.

However, perhaps you don't want an interactive shell. In this case, there are a few options:

1. Comment out the return line. You can use something like this in your Dockerfile:

RUN sed -e '/[ -z "$PS1" ] && return/s/^/#/g' -i /root/.bashrc

This modification to .bashrc will prevent its early exit from non-interactive invocations.

2. Move the nvm setup to .profile. Move the last three lines of your .bashrc file to .profile so they're executed unconditionally.

3. Manually source .bashrc. As other answers have already noted, you can certainly manually source .bashrc as needed, as in,

RUN source /root/.bashrc && /workspace/launch.sh

Observe that much of the content of .bashrc makes the most sense for interactive shells and is usually unnecessary otherwise, which may make option 2 above the most appealing.

Chuck Batson
  • 2,165
  • 1
  • 17
  • 15
  • 1
    Well written answer — thank you, it helped me too. I used something like `echo "alias ls='ls -1AoghpG --color=auto'" > /root/.bashrc` in my `entrypoint.sh` file, which was run with the `ENTRYPOINT` command in my Dockerfile. – jv-k Oct 25 '22 at 07:11
  • 1
    Helped me too, thanks. However when I go `FROM debian:bookworm-slim` there is no PS1 in `.bashrc`, it checks for interactivity another way. I ended up doing `RUN sed -i '1s;^;#1st line here...\n';`. Also what really helped was having right after the `FROM` line the `SHELL ["/bin/bash", "-lc"]` so I don't need to remember to prefix every relevant `RUN` with it or with `source ~/.bashrc;` – V-R Jun 15 '23 at 14:17
9

with CMD and shell form

CMD /bin/bash -i "/workspace/launch.sh"

Edit should also work with ENTRYPOINT and and using exec form using

ENTRYPOINT ["bash","-i","/workspace/entrypoint.sh"]

I believe the -i flag works in the intended way, the .bashrc file is used as intended, the other solutions did not work for me, the .bashrc file was never used

solution may not be ideal for everyone, with the -i flag the program may prompt for user interaction

ps: I used docker create and docker start -i "container name"

daniel reis
  • 91
  • 1
  • 3
  • 1
    This is the solution that worked for me (not using CMD but `ENTRYPOINT ["bash","-i","/app/entrypoint.sh"]` – Nelson Rodriguez Oct 26 '21 at 21:51
  • that may be because you were overriding CMD in docker run, and always required some setup beforehand with ENTRYPOINT?, asking because I was thinking they should have worked the same @NelsonRodriguez – daniel reis Nov 03 '21 at 13:02
  • 1
    This worked. I realized that doing `bash -i` will load ~/.bashrc, while doing `bash -l` will load ~/.profile. – zkytony Jun 09 '23 at 15:33
3

You can add source /path/to/bashrc in launch.sh and change the CMD to the following instead of changing to bash through CMD itself:

CMD ["/workspace/launch.sh"]

Alternatively, You can do the following in your Dockerfile instead of depending on bashrc

ENV NVM_DIR /root/.nvm
ENV NODE_VERSION 7.9.0
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules #Ensure that this is the actual path
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
RUN . $NVM_DIR/nvm.sh && \
  nvm install $NODE_VERSION && npm install -g npm@5.6.0
Mostafa Hussein
  • 11,063
  • 3
  • 36
  • 61