Keywords: Dockerfile | Environment Variables | COPY Instruction | User Permissions | Image Building
Abstract: This technical article provides an in-depth analysis of why the $HOME environment variable fails to work properly in Dockerfile ADD/COPY instructions. By examining Docker's build process mechanisms, user switching, and environment variable scoping, it reveals the fundamental differences between COPY and RUN instructions in environment variable handling. The article presents two practical solutions: explicitly setting HOME using ENV directive, or using temporary directory staging with RUN commands. It also discusses file ownership issues and corresponding chown strategies, offering comprehensive guidance for user permission management in Docker image building.
Analysis of Environment Variable Scope in Docker Build Process
During Dockerfile development, engineers frequently encounter inconsistent behavior of environment variables across different instructions. A classic case is the failure of $HOME variable in COPY instructions. Based on in-depth community discussions and validated best practices, this phenomenon stems from fundamental differences in execution mechanisms between Docker build instructions.
Interaction Mechanism Between USER and RUN Instructions
When using the USER directive to switch users in a Dockerfile, it affects all subsequent instructions that start new processes within the container. For example, in the following code snippet:
FROM ubuntu:utopic
RUN useradd -m aptly
USER aptly
RUN echo $HOME
The RUN echo $HOME command outputs /home/aptly. This occurs because the RUN instruction starts a new shell process inside the container, which inherits the user environment set by the preceding USER directive, including the user's home directory path.
Special Characteristics of COPY Instruction
Unlike the RUN instruction, the COPY directive does not start a new shell process within the container. Docker's build engine simply copies files from the build context to the image's filesystem when executing COPY, without involving any shell environment. Consequently, COPY instructions cannot access user environment variables set via the USER directive, including $HOME.
This explains why in the problem example, despite switching to the aptly user with USER aptly, the subsequent COPY ./public.key $HOME/public.key instruction fails to properly resolve the $HOME variable, causing files to be copied to incorrect locations (effectively the root directory), ultimately making the gpg --import command unable to find the expected key files.
Solution One: Explicit Environment Variable Setting
The most direct and effective solution is to explicitly set the HOME environment variable in the Dockerfile:
ENV HOME /home/aptly
Variables set via the ENV directive are available throughout the entire build process, including within COPY instructions. With this modification, COPY ./.aptly.conf $HOME/.aptly.conf will correctly copy the file to /home/aptly/.aptly.conf.
Solution Two: Temporary Directory Staging
An alternative approach is to first copy files to a temporary location, then move them to the target location using RUN instructions:
COPY ./.aptly.conf /tmp/.aptly.conf
RUN cp /tmp/.aptly.conf $HOME/.aptly.conf
This method leverages the fact that RUN instructions can access shell environment variables. While it adds build steps, it provides greater flexibility in complex scenarios.
File Ownership Management
Regardless of the chosen solution, file ownership considerations are crucial. By default, files added to the image via COPY instructions have root as both owner and group. If these files need to be accessed or modified by non-root users, ownership must be explicitly changed:
COPY ./.aptly.conf $HOME/.aptly.conf
RUN chown aptly:aptly $HOME/.aptly.conf
Or when using the temporary directory approach:
COPY ./.aptly.conf /tmp/.aptly.conf
RUN cp /tmp/.aptly.conf $HOME/.aptly.conf && \
chown aptly:aptly $HOME/.aptly.conf
Best Practice Recommendations
Based on the above analysis, we recommend the following Dockerfile authoring best practices:
- Explicitly set the
HOMEenvironment variable immediately after creating the user - Use paths defined by
ENVvariables for files that need to be copied to user home directories - Always consider file ownership issues and use
chownadjustments when necessary - Maintain logical instruction ordering: set users and environment variables first, then perform file operations
A corrected Dockerfile core section example:
RUN useradd -m aptly
ENV HOME /home/aptly
USER aptly
COPY ./.aptly.conf $HOME/.aptly.conf
COPY ./public.key $HOME/public.key
COPY ./signing.key $HOME/signing.key
RUN chown -R aptly:aptly $HOME
Extended Technical Principles
This phenomenon reveals an important characteristic of Docker's build process: only instructions that actually start processes within the container (such as RUN, CMD, ENTRYPOINT) can access the complete shell environment. File operation instructions like COPY and ADD are handled directly by the Docker engine without passing through a shell interpreter. This design ensures build efficiency while introducing environment variable scope differences that developers must carefully consider when writing Dockerfiles.