49

I am trying to create a docker image that will setup a Linux environment for building Rust projects. Here is my Dockerfile so far:

FROM ubuntu:16.04

# Update default packages
RUN apt-get update

# Get Ubuntu packages
RUN apt-get install -y \
    build-essential \
    curl

# Update new packages
RUN apt-get update

# Get Rust
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y

The last thing I need to do is configure Rust, so that I can use cargo. The documentation says to use

source $HOME/.cargo/env

but when I try that in a RUN command in a Dockerfile, it says source is not recognized. Another option I found was to use

RUN /bin/bash -c "source ~/.cargo/env"

This does not error, but when I run my container, cargo is not a recognized command.

Either approach works from Bash when I have the container open, but I would like this to be automated as part of the image.

How can I integrate this into my Dockerfile?

2
  • bash -c 'source anything' only sources a file into that single bash instance, which exits when the command completes. The right way to use it is bash -c 'source ~/.cargo/env && cargo whatever', running the source in the same shell that actually needs to use what you're adding. Commented Apr 5, 2018 at 16:12
  • (also, consider . instead of source -- the latter name is a bashism, whereas the former is POSIX-mandated). Commented Apr 5, 2018 at 16:15

4 Answers 4

76

You have to add the sourcing inside the .bashrc.

This works:

FROM ubuntu:16.04

# Update default packages
RUN apt-get update

# Get Ubuntu packages
RUN apt-get install -y \
    build-essential \
    curl

# Update new packages
RUN apt-get update

# Get Rust
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y

RUN echo 'source $HOME/.cargo/env' >> $HOME/.bashrc

EDIT

Instead of

RUN echo 'source $HOME/.cargo/env' >> $HOME/.bashrc

you can use

ENV PATH="/root/.cargo/bin:${PATH}"

which is a less bash-only solution

7
  • 1
    .bashrc is not honored by (all) noninteractive shells. It's thus not a particularly appropriate option for this use case. Commented Apr 5, 2018 at 16:14
  • I am aware of that, I answered like that because it seemed to me that that was what he was trying to do: use an interactive bash inside the docker container and having cargo commands available Commented Apr 5, 2018 at 16:29
  • 5
    You can go with ENV PATH="/root/.cargo/bin:${PATH}", it's a good and transparent way of making cargo visible. P.S. note /root in my example, you can build as root via USER root in dockerfile, or you can change /root to desired dir.
    – folex
    Commented Sep 30, 2019 at 14:02
  • 1
    The solution with ENV PATH=... seems to generally work better with most docker images Commented Sep 26, 2020 at 11:53
  • 2
    -s means operate silently. -- is used to separate the options passed to sh from the positional parameters that follow. -y - is a positional parameter passed to the script executed by sh - here rust script.
    – Yurkee
    Commented Jul 7, 2023 at 17:34
41

The only thing source ~/.cargo/env does is

export PATH="$HOME/.cargo/bin:$PATH"

So my suggestion is to set PATH explicitly in your Dockerfile:

FROM ubuntu:16.04

# Update default packages
RUN apt-get -qq update

# Get Ubuntu packages
RUN apt-get install -y -q \
    build-essential \
    curl

# NOTE: no need to run update again at this point
# RUN apt-get update

# Get Rust; NOTE: using sh for better compatibility with other base images
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y

# Add .cargo/bin to PATH
ENV PATH="/root/.cargo/bin:${PATH}"

# Check cargo is visible
RUN cargo --help

This is slightly more transparent than source .cargo/env, and easier to grasp if you're not familiar with Rust.

Also, note that there are rust images on DockerHub, you can use them (in FROM, instead of ubuntu:16.04) for running or building Rust apps. It will be faster and easier than installing everything via apt and curl.

For building apps there's a possibility you will find multistage docker builds useful. They are pretty flexible.

3
  • 3
    One more option is to replace ENV PATH="/root/.cargo/bin:${PATH}" with SHELL [ "/bin/sh", "-s", ".", "$HOME/.cargo/env" ] which prepends the source to each command after this.
    – blanNL
    Commented May 25, 2022 at 18:33
  • 1
    Wow, interesting! Why do that, what's the pro? @blanNL
    – folex
    Commented Nov 1, 2022 at 18:50
  • 2
    This does not seem to work. I'm using Ubuntu 22.04 in my container, and it can't find cargo (RUN cargo --help fails)
    – WolfLink
    Commented Dec 9, 2022 at 4:34
4

I think you may be misunderstanding what source does. This built-in command tells the current shell to load the following code (almost) as if it were being run at the current prompt (you can also use source inside other scripts). It's basically an "include file here" command. It's mostly used to set up your environment (PATH, LIBPATH, and other shell functions), not to do real work.

Running "source" in a RUN command is thus (almost always) useless. It will load up the cargo environment and then exit, thus losing all the environment changes.

This leaves you with two basic options. One is to do as michael_bitard suggests, and get it added to your .bashrc. This will mean that all commands forevermore in that container (by that user) will have the environment set up. If you only need this for setup, then it pollutes your shell environment at runtime.

The second option is to basically run the source as part of every RUN command that needs it in the rest of your Dockerfile. RUN bash -c 'source $HOME/.cargo/env; command goes here for example. This is more work on each RUN line, but the environment will be explicitly there when you need it, and not when you don't.

Most of the time, the first option is what you want. Rarely you want the second. That is, sometimes you only need this environment for setup purposes, and you don't want it to persist - this is rare, though I've had that scenario come up a few times.

1
  • Thanks. I did not understand the nature of source at all. I am very new to Bash and Linux.
    – JamesFaix
    Commented Apr 5, 2018 at 16:45
1

source (or .) is a shell built in command - it runs the commands from the file given as parameter in the current shell. This is useful for configuring your current environment (aliases, functions and environment variables) configured in a script does not apply to the parent shell that started it, with source those can be configured. This is why RUN fails - there is no executable called source. (It might work with the shell form of RUN or with SHELL)

To run a command with items set up from that file, you should be able to get away with:

RUN bash -c 'source ~/.cargo/env; cargo <whatever>'

If you want the variables to be applied to a shell inside the container, you need to add the source command to one of the bash files that is sourced when the shell is started, e.g. ~/.bashrc or ~/.bash_profile (depending on whether a login shell is started or not) or (on most distributions) a .sh file in /etc/profile.d/

One way to do this would be the following in your Dockerfile:

RUN ln -s $HOME/.cargo/env /etc/profile.d/cargo_env.sh

Not the answer you're looking for? Browse other questions tagged or ask your own question.