Using the RUN instruction in a Dockerfile with 'source' does not work
Categories:
Why 'source' in Dockerfile RUN instruction fails and how to fix it

Understand the nuances of shell execution in Dockerfiles and learn effective methods to execute scripts and set environment variables using the RUN instruction.
When building Docker images, developers often encounter unexpected behavior when trying to use the source command (or its alias .) within a RUN instruction in a Dockerfile. This typically manifests as environment variables not being set or scripts not executing as intended. This article delves into the reasons behind this issue and provides robust solutions to ensure your Docker builds behave predictably.
The Problem: Shell Execution Context in Dockerfiles
The core of the problem lies in how Docker executes RUN instructions. By default, each RUN instruction is executed in a new shell context, typically /bin/sh -c (or /bin/bash -c if specified). When you use source or . to execute a script, it runs the script in the current shell context, meaning any environment variables or functions defined by that script become available in that specific shell session. However, once that RUN instruction completes, the shell session is terminated, and all its context (including sourced variables) is lost.
FROM alpine:latest
# This will NOT work as expected
RUN echo 'export MY_VAR="hello"' > /tmp/script.sh
RUN source /tmp/script.sh
RUN echo $MY_VAR
Example of source failing to persist environment variables across RUN instructions.
In the example above, the source /tmp/script.sh command successfully sets MY_VAR within its own shell context. However, the subsequent RUN echo $MY_VAR command executes in a new shell context, which has no knowledge of MY_VAR, resulting in an empty output.
flowchart TD
A[Dockerfile RUN Instruction 1] --> B{"Shell Context 1 (/bin/sh -c)"}
B --> C["Execute 'source script.sh'"]
C --> D["MY_VAR set in Shell Context 1"]
D --> E["Shell Context 1 Exits"]
E --x F["MY_VAR is Lost"]
F --> G[Dockerfile RUN Instruction 2]
G --> H{"Shell Context 2 (/bin/sh -c)"}
H --> I["Execute 'echo $MY_VAR'"]
I --> J["MY_VAR is Undefined in Shell Context 2"]
J --> K["Empty Output"]Flowchart illustrating why source fails across separate RUN instructions.
Solutions for Persistent Environment Variables and Script Execution
To correctly set environment variables or execute scripts that modify the shell environment for subsequent RUN instructions, you need to ensure they operate within the same shell context or use Docker's built-in mechanisms.
ENV instruction for setting static environment variables. For dynamic variables or script execution, combine commands within a single RUN instruction or use SHELL.Method 1: Combine Commands in a Single RUN Instruction
The most straightforward solution is to combine all related commands, including the source command and any commands that rely on its output, into a single RUN instruction. This ensures they all execute within the same shell context.
FROM alpine:latest
# This will work
RUN echo 'export MY_VAR="hello from combined run"' > /tmp/script.sh && \
source /tmp/script.sh && \
echo "Value of MY_VAR: $MY_VAR"
Correctly sourcing a script and using its variables within a single RUN instruction.
Method 2: Use the ENV Instruction
For setting simple, static environment variables, the ENV instruction is the most idiomatic and recommended Docker approach. Variables set with ENV persist across all subsequent RUN, CMD, and ENTRYPOINT instructions, and are also available in the final container.
FROM alpine:latest
ENV MY_VAR="hello from ENV"
RUN echo "Value of MY_VAR: $MY_VAR"
Using the ENV instruction to set a persistent environment variable.
Method 3: Change the Default Shell (Advanced)
If you have complex scripting needs or prefer a specific shell (e.g., bash for its advanced features), you can change the default shell used by RUN instructions with the SHELL instruction. This is less common for simply sourcing variables but can be useful for specific build environments.
FROM ubuntu:latest
# Install bash if not present (e.g., on Alpine)
RUN apt-get update && apt-get install -y bash
# Set the default shell for subsequent RUN instructions
SHELL ["/bin/bash", "-c"]
RUN echo 'export MY_VAR="hello from bash"' > /tmp/script.sh && \
source /tmp/script.sh && \
echo "Value of MY_VAR: $MY_VAR"
Changing the default shell to bash for RUN instructions.
SHELL can have implications for image size and compatibility. Use it judiciously.Best Practices
To avoid issues and maintain clear, efficient Dockerfiles:
1. Combine related commands
Use && \ to chain multiple commands into a single RUN instruction, especially when one command's output or environment changes are needed by the next.
2. Use ENV for static variables
For environment variables that are known at build time and don't require complex logic, ENV is the cleanest and most efficient method.
3. Minimize RUN layers
Each RUN instruction creates a new layer. Combining commands reduces the number of layers, leading to smaller and faster images. Remember to clean up temporary files in the same RUN instruction where they were created.
4. Understand shell differences
Be aware of the differences between /bin/sh (often dash on Debian/Ubuntu) and /bin/bash. If your scripts rely on bash-specific features, explicitly set SHELL or ensure bash is installed and used.