2

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?

hobscrk777
  • 2,347
  • 4
  • 23
  • 29
  • 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). – David Maze Apr 30 '22 at 09:51

4 Answers4

5

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 -
Rik
  • 676
  • 7
  • 11
  • I've built your Dockerfile (as-of original answer from Oct 8, 2022 at 18:50) and then "docker run -ti 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. – V-R Jun 15 '23 at 13:07
  • 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;" – V-R Jun 15 '23 at 13:19
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.

larsks
  • 277,717
  • 41
  • 399
  • 399
2

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

Dakota Hipp
  • 749
  • 7
  • 16
0

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 profile, so it is available in `docker run`
ENV PATH="$PATH:/root/.asdf/bin"

# 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.

Razzi Abuissa
  • 3,337
  • 2
  • 28
  • 29