Can install asdf inside Docker container, but not at build time via Dockerfile
Asked Answered
J

5

7

I have a Dockerfile in which I am trying to install and use asdf to manage Python package versions. A snippet of my Dockerfile appears below.


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

RUN git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.10.0
RUN chmod +x ~/.asdf/asdf.sh ~/.asdf/completions/asdf.bash
RUN echo ". $HOME/.asdf/asdf.sh" >> ~/.bashrc
RUN echo ". $HOME/.asdf/completions/asdf.bash" >> ~/.bashrc
ENV PATH="$HOME/.asdf/bin:$HOME/.asdf/shims:$PATH"
ENV PATH="$HOME/.asdf:$PATH"
RUN echo -e '\nsource $HOME/.asdf/asdf.sh' >> ~/.bashrc
RUN source ~/.bashrc
RUN bash -c 'echo -e which asdf'
RUN asdf plugin-add python

That last line is the offending line. When I try to build this Docker image, I get the following.

 => ERROR [17/19] RUN asdf plugin-add python                                                                                                                    0.3s
------
 > [17/19] RUN asdf plugin-add python:
#21 0.292 /bin/bash: asdf: command not found
------
executor failed running [/bin/bash -c asdf plugin-add python]: exit code: 127

However, if I remove that line, I'm able to run a container and then just immediately run asdf successfully.

docker run -it <image ID>
root:# asdf plugin-add python
initializing plugin repository...Cloning into '/root/.asdf/repository'...
<etc>

Why doesn't this work when I try to run it through the Dockerfile?

Jerol answered 29/4, 2022 at 20:22 Comment(1)
Can you just use one of the Docker Hub python images? Often version managers like asdf don't work well in Docker, since shell dotfiles usually aren't used at all (you are not running an "interactive" or "login" shell).Sooth
T
7

I found that source ~/.bashrc; just doesn't always do it in a docker container (it doesn't in a live OS at times), nor does /bin/bash -c 'source ~/.bashrc';.

The trick to installing asdf in a Docker container at build time, I found, was to go as far as to restart bash (i.e. exec bash). Possibly it has to do with the fact that we're not actually modifying something like PATH= and subsequently source <file>'ing it, but using posix compliant source directives to alias an executable script by the filename (minus its extension).

. $HOME/.asdf/asdf.sh

Means:

source /home/user/.asdf/asdf.sh

Which seemingly gives the effect of:

alias asdf=/home/user/.asdf/asdf.sh

Simply sourcing the file won't work because the install instructions (provided by asdf authors) provide essentially a source directive to place inside of ~/.bashrc, and I believe the context then becomes one other than the one the shell we're using is subsequently under.

To fix this, we have to restart bash - there's no other way.

We'll also run into a lot of quirks and issues we have to circumvent when trying to configure user packages (just semantics, really) as root, so to avoid that its best to establish a non-root user to work with.

Here's a working example that goes further to install Ruby and NodeJS:

FROM debian:bookworm-slim
# .. LABEL, etc., ...
#
RUN apt-get update && \
# prep tools also for asdf (last 2)
    apt-get install -y curl git \
        software-properties-common \
        gnupg2 apt-transport-https \
# prep deps for asdf-ruby
        build-essential autoconf \
        bison patch rustc \
        libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libgmp-dev \
        libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev uuid-dev \
# prep deps for asdf-nodejs
        dirmngr gawk \
# prep deps for non-root user
        sudo; \
# create special user
    useradd --create-home --shell /bin/bash gitlab; \
    /bin/bash -c 'echo "gitlab:password" | chpasswd'; \
    adduser gitlab sudo

## change user for all subsequent commands
USER gitlab

# change working directory
WORKDIR /home/gitlab

# install asdf
RUN \
    # configure git to get rid of detached head warnings
    git config --global advice.detachedHead false; \
    git clone https://github.com/asdf-vm/asdf.git $HOME/.asdf --branch v0.10.2; \
    /bin/bash -c 'echo -e "\n\n## Configure ASDF \n. $HOME/.asdf/asdf.sh" >> ~/.bashrc'; \
    /bin/bash -c 'echo -e "\n\n## ASDF Bash Completion: \n. $HOME/.asdf/completions/asdf.bash" >> ~/.bashrc'; \
    exec bash; \
# install asdf-ruby
    /bin/bash -c asdf plugin add ruby https://github.com/asdf-vm/asdf-ruby.git; \
# install asdf-nodejs
    /bin/bash -c asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git;

# whatever you now want to do -
Th answered 8/10, 2022 at 18:50 Comment(2)
I've built your Dockerfile (as-of original answer from Oct 8, 2022 at 18:50) and then "docker run -ti <hash> bash". Once inside, I can run "asdf" but "asdf plugin list" says "No plugins installed". If I remove the "exec bash;" line, it fails to build due to "plugin: line 1: asdf: command not found" which becomes "/bin/bash: line 1: asdf: command not found" if I surround the asdf commands with single-quotes like the echo commands above.Bracelet
Got your Dockerfile (as-of original answer from Oct 8, 2022 at 18:50) to return 2 plugins from "asdf plugin list" by removing the "exec bash;" line, wrapping both "asdf" commands in single-quotes like the "echo" commands above, and prefixing both "asdf" commands with "source ~/.asdf/asdf.sh;"Bracelet
P
4

The exec bash suggestion in this answer wasn't working for me, but I did find you can specify the shell in your dockerfile and bash changes will be persisted with each RUN command. Ex. SHELL ["/bin/bash", "-lc"]

Thanks to this github user and gist for showing me this, also it is a pretty extensive asdf Dockerfile. https://gist.github.com/BrutalSimplicity/882af1d343b7530fc7e005284523d38d

Perineuritis answered 6/5, 2023 at 7:40 Comment(1)
It seems like this solution doesn't work for the latest version of buildkit. Some comments in that gist have work arounds, but that means this probably isn't the best path forward at this point.Storeroom
N
2

This...

RUN source ~/.bashrc

Does absolutely nothing. Each RUN command executes in a new shell, which exits when the command completes. Sourcing bash scripts, setting variables, and other things that modify the current environment will not persist to subsequent RUN commands.

You could modify your Dockerfile to run a sequence of commands in a RUN command, like this:

RUN source ~/.bashrc; \
  asdf plugin-add python

...which would at least successfullly source the .bashrc file and presumably make the asdf tool available.

Napoli answered 29/4, 2022 at 21:16 Comment(0)
B
2

I have found that after cloning asdf to install it, the easiest way to get the asdf command to run is by setting PATH to include it:

ENV PATH="$PATH:/root/.asdf/bin"

Full example:

FROM debian:12

# Install asdf dependencies
RUN apt-get update -y && apt-get install -y \
  curl \
  git

# Install asdf
RUN git clone --depth 1 https://github.com/asdf-vm/asdf.git ~/.asdf

# Add asdf to PATH, so it can be run in this Dockerfile
ENV PATH="$PATH:/root/.asdf/bin"

# Add asdf shims to PATH, so installed executables can be run in this Dockerfile
ENV PATH=$PATH:/root/.asdf/shims

# Now you're free to install asdf plugins:
RUN asdf plugin add nodejs
RUN asdf install nodejs 20.5.1
RUN asdf global nodejs 20.5.1

I also recommend looking at this Dockerfile which has a more robust configuration. You can either use that as a base image, or just read its commands for inspiration.

Barbarossa answered 13/8, 2023 at 3:25 Comment(1)
ENV worked for me.Developer
U
0

As mentioned already, running

RUN source ~/.bashrc

doesn't help – the file is sourced for a different shell instance. Setting shell to bash with --login option makes every instance after

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

command to read configuration files though. Therefore your Dockerfile becomes:

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

RUN git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.10.0
RUN echo ". $HOME/.asdf/asdf.sh" >> ~/.bashrc
RUN echo ". $HOME/.asdf/completions/asdf.bash" >> ~/.bashrc
RUN asdf plugin-add python

As you also don't need those few other lines, which you most probably added while debugging your Dockerfile ;-)

Update:

obviously your chain of config files needs to source .bashrc. Something like

if [ -f ~/.bashrc ]; then
    source ~/.bashrc
fi

in .bash_profile should do. Alternatively using .bash_profile instead of .bashrc should also do. More information in bash man page - section INVOCATION

Unstable answered 3/3 at 23:28 Comment(2)
This does not work with the latest buildkit: > [5/6] RUN asdf --version: 0.180 /bin/bash: line 1: asdf: command not found ------ asdf.Dockerfile:9 -------------------- 7 | RUN git clone github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0 8 | RUN echo ". $HOME/.asdf/asdf.sh" >> ~/.bashrc 9 | >>> RUN asdf --versionStoreroom
@Storeroom From 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. Check if any of the listed configuration files is present and reads .bashrc. Something like this: if [ -f ~/.bashrc ]; then source ~/.bashrc fi in .bash_profile should do.Unstable

© 2022 - 2024 — McMap. All rights reserved.