Search code examples
dockershelldockerfile

Interpretation of special shell variables in Dockerfile


I'm a docker newbie and I'm learning how to write Dockerfile. I've been experimenting with different ways of writing RUN instructions, and I've come across some behavior that I don't understand why, so I'd like to know about it.

When I built and run the following Dockerfile.v1, the sample_file exists in the sample_dir. This is as expected.

FROM ubuntu:latest
RUN mkdir sample_dir && cd sample_dir && touch sample_file

Then, I decided to use shell variables for general purpose writing, I created the following Dockerfile.v2 and RUN.

FROM ubuntu:latest
RUN mkdir sample_dir && cd $_ && touch sample_file

Then, sample_file was created in /root, which is not what I expected. Since the sample_dir itself was created in /, I assume WORKING DIRECTORY is /.

Why was it created in the root user's home directory? How was $_ interpreted?

❯ docker run -it --rm 1ce bash                                                                          2023/09/17 18:01:16
root@2ce4010e4613:/# 
root@2ce4010e4613:/# ll
total 60
drwxr-xr-x   1 root root 4096 Sep 17 09:01 ./
drwxr-xr-x   1 root root 4096 Sep 17 09:01 ../
-rwxr-xr-x   1 root root    0 Sep 17 09:01 .dockerenv*
lrwxrwxrwx   1 root root    7 Aug 16 02:25 bin -> usr/bin/
drwxr-xr-x   2 root root 4096 Apr 18  2022 boot/
drwxr-xr-x   5 root root  360 Sep 17 09:01 dev/
drwxr-xr-x   1 root root 4096 Sep 17 09:01 etc/
drwxr-xr-x   2 root root 4096 Apr 18  2022 home/
lrwxrwxrwx   1 root root    7 Aug 16 02:25 lib -> usr/lib/
drwxr-xr-x   2 root root 4096 Aug 16 02:25 media/
drwxr-xr-x   2 root root 4096 Aug 16 02:25 mnt/
drwxr-xr-x   2 root root 4096 Aug 16 02:25 opt/
dr-xr-xr-x 212 root root    0 Sep 17 09:01 proc/
drwx------   1 root root 4096 Sep 17 07:39 root/
drwxr-xr-x   5 root root 4096 Aug 16 04:54 run/
drwxr-xr-x   2 root root 4096 Sep 17 07:39 sample_dir/
lrwxrwxrwx   1 root root    8 Aug 16 02:25 sbin -> usr/sbin/
drwxr-xr-x   2 root root 4096 Aug 16 02:25 srv/
dr-xr-xr-x  13 root root    0 Sep 17 09:01 sys/
drwxrwxrwt   2 root root 4096 Aug 16 04:52 tmp/
drwxr-xr-x  11 root root 4096 Aug 16 02:25 usr/
drwxr-xr-x  11 root root 4096 Aug 16 04:52 var/
root@2ce4010e4613:/# ll /root/
total 16
drwx------ 1 root root 4096 Sep 17 07:39 ./
drwxr-xr-x 1 root root 4096 Sep 17 09:01 ../
-rw-r--r-- 1 root root 3106 Oct 15  2021 .bashrc
-rw-r--r-- 1 root root  161 Jul  9  2019 .profile
-rw-r--r-- 1 root root    0 Sep 17 07:39 sample_file
root@2ce4010e4613:/# 

I know I can avoid this behavior by not using $_ or by using WORKDIR instruction, but I just want to know why this is happening.

I thought it might be because the default is /bin/sh, so I specified bash in SHELL INSTRUCTION as shown below, but there was no change.

https://docs.docker.com/engine/reference/builder/#shell

FROM ubuntu:latest
RUN mkdir sample_dir && cd $_ && touch sample_file
SHELL [ "/bin/bash" ]

My environment

❯ docker --version                                                                                                                                                                                      2023/09/19 21:38:37
Docker version 24.0.5, build ced0996

❯ sw_vers                                                                                                                                                                                               2023/09/19 21:42:27
ProductName:            macOS
ProductVersion:         13.5.2
BuildVersion:           22G91

detailed info

❯ docker build -f ./Dockerfile_old .                                                                    2023/10/03 18:13:48
[+] Building 0.2s (6/6) FINISHED                                                                       docker:desktop-linux
 => [internal] load build definition from Dockerfile_old                                                               0.0s
 => => transferring dockerfile: 140B                                                                                   0.0s
 => [internal] load .dockerignore                                                                                      0.0s
 => => transferring context: 2B                                                                                        0.0s
 => [internal] load metadata for docker.io/library/ubuntu:latest                                                       0.0s
 => [1/2] FROM docker.io/library/ubuntu:latest                                                                         0.0s
 => [2/2] RUN mkdir sample_dir && cd $_ && touch sample_file                                                           0.1s
 => exporting to image                                                                                                 0.0s
 => => exporting layers                                                                                                0.0s
 => => writing image sha256:3f2dc4ad66693b6306ee812371b79e6a2984a18e6590509be9a98108a235f7ff                           0.0s

❯ docker history --no-trunc 3f2                                                                         2023/10/03 18:15:25
IMAGE                                                                     CREATED          CREATED BY                                                                                          SIZE      COMMENT
sha256:3f2dc4ad66693b6306ee812371b79e6a2984a18e6590509be9a98108a235f7ff   37 seconds ago   RUN /bin/sh -c mkdir sample_dir && cd $_ && touch sample_file # buildkit                            0B        buildkit.dockerfile.v0
<missing>                                                                 7 days ago       /bin/sh -c #(nop)  CMD ["/bin/bash"]                                                                0B        
<missing>                                                                 7 days ago       /bin/sh -c #(nop) ADD file:8540670760767f19eaf101fbce1da1881a2f24a7d65da6abdedc644b8fb00463 in /    69.2MB    
<missing>                                                                 7 days ago       /bin/sh -c #(nop)  LABEL org.opencontainers.image.version=22.04                                     0B        
<missing>                                                                 7 days ago       /bin/sh -c #(nop)  LABEL org.opencontainers.image.ref.name=ubuntu                                   0B        
<missing>                                                                 7 days ago       /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH                                                         0B        
<missing>                                                                 7 days ago       /bin/sh -c #(nop)  ARG RELEASE                                                                      0B        

❯ docker images | grep ubuntu                                                                                                                                                                                                                    2023/10/03 18:16:23
ubuntu                  latest    bf9e5b7213b4   7 days ago      69.2MB

Solution

  • $_ only really makes sense in interactive Bash. A more standard and portable solution is to use a regular variable.

    RUN d=sample_dir && mkdir "$d" && cd "$d" && touch sample_file
    

    ... though of course, the cd is pretty much useless here.

    RUN d=sample_dir && mkdir "$d" && touch "$d"/sample_file
    

    Anyway, your attempt to set SHELL is wrong. You want

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

    Without the -c, your RUN statements would be interpreted as names of shell script files to try to execute.

    Of course, when $_ is empty, cd $_ expands to just cd which changes to the current user's home directory.