Dockerfile
PATH
environment-variables
Docker
software-development

In a Dockerfile, How to update PATH environment variable?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

To update the PATH environment variable in a Dockerfile, use the ENV instruction: ENV PATH="/your/directory:${PATH}". This prepends your directory to the existing PATH and persists across all subsequent layers, RUN commands, and the final container.

How PATH Works in Docker

The PATH environment variable tells the shell which directories to search when you type a command. In Docker images, PATH is inherited from the base image. For example, the official ubuntu image sets PATH to /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin. When you install software in a custom directory, you need to add that directory to PATH so the binaries are available without specifying full paths.

The ENV Instruction

The ENV instruction sets environment variables that persist in the image metadata. Unlike RUN export, which only affects the current shell session within a single RUN layer, ENV is permanent.

dockerfile
# Prepend a custom directory to PATH
ENV PATH="/opt/myapp/bin:${PATH}"

The ${PATH} reference expands to the current value of PATH, so existing directories are preserved. After this line, any subsequent RUN, CMD, or ENTRYPOINT instruction can find executables in /opt/myapp/bin.

Complete Working Example

Here is a Dockerfile that installs a custom tool and adds it to PATH:

dockerfile
1FROM ubuntu:22.04
2
3# Install dependencies
4RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
5
6# Download and install a tool to a custom directory
7RUN mkdir -p /opt/mytool/bin && \
8    echo '#!/bin/bash\necho "Hello from mytool"' > /opt/mytool/bin/mytool && \
9    chmod +x /opt/mytool/bin/mytool
10
11# Add the custom directory to PATH
12ENV PATH="/opt/mytool/bin:${PATH}"
13
14# This works because PATH now includes /opt/mytool/bin
15CMD ["mytool"]

Build and run it:

bash
docker build -t path-demo .
docker run --rm path-demo
# Output: Hello from mytool

Prepending vs. Appending

The order of directories in PATH determines which binary runs when multiple directories contain executables with the same name. The shell searches left to right and uses the first match.

dockerfile
1# Prepend: your directory is searched first
2ENV PATH="/opt/myapp/bin:${PATH}"
3
4# Append: your directory is searched last
5ENV PATH="${PATH}:/opt/myapp/bin"

Prepending is the standard practice when you want your custom binaries to take priority over system defaults. For example, if you install a newer version of Python in /opt/python3.12/bin, prepending ensures that python3 resolves to your version rather than the system-installed one.

Real-World Patterns

Python Virtual Environment

dockerfile
1FROM python:3.12-slim
2
3RUN python -m venv /opt/venv
4ENV PATH="/opt/venv/bin:${PATH}"
5
6COPY requirements.txt .
7RUN pip install -r requirements.txt
8
9COPY . /app
10WORKDIR /app
11CMD ["python", "main.py"]

Go Binary Installation

dockerfile
1FROM golang:1.22 AS builder
2WORKDIR /src
3COPY . .
4RUN go build -o /app/server .
5
6FROM debian:bookworm-slim
7COPY --from=builder /app/server /opt/app/server
8ENV PATH="/opt/app:${PATH}"
9CMD ["server"]

Node.js Local Binaries

dockerfile
1FROM node:20-slim
2WORKDIR /app
3COPY package*.json ./
4RUN npm ci
5ENV PATH="/app/node_modules/.bin:${PATH}"
6COPY . .
7CMD ["next", "start"]

Java with Custom JAVA_HOME

dockerfile
1FROM ubuntu:22.04
2
3RUN apt-get update && apt-get install -y openjdk-17-jdk-headless && \
4    rm -rf /var/lib/apt/lists/*
5
6ENV JAVA_HOME="/usr/lib/jvm/java-17-openjdk-amd64"
7ENV PATH="${JAVA_HOME}/bin:${PATH}"
8
9COPY app.jar /opt/app.jar
10CMD ["java", "-jar", "/opt/app.jar"]

ENV vs. RUN export vs. Shell Profile

MethodPersists Across Layers?Available in CMD/ENTRYPOINT?Available in docker exec?
ENV PATH=...YesYesYes
RUN export PATH=...NoNoNo
RUN echo 'export PATH=...' >> ~/.bashrcOnly in interactive bashOnly if shell reads profileOnly in bash -l

RUN export PATH=... is a common mistake. Each RUN instruction starts a new shell, so the export is lost as soon as the RUN layer finishes. The ENV instruction is the correct approach because it writes the variable into the image metadata, making it available everywhere.

Writing to .bashrc or .profile is fragile because these files are only read by interactive login shells. CMD ["myapp"] (exec form) does not start a shell at all, so profile scripts are never sourced.

Debugging PATH Issues

When a command is "not found" despite being installed, inspect PATH inside the container:

bash
1# Check the current PATH
2docker run --rm your-image env | grep PATH
3
4# Find where a binary actually lives
5docker run --rm your-image which python3
6
7# List all directories in PATH, one per line
8docker run --rm your-image bash -c 'echo "$PATH" | tr ":" "\n"'

You can also verify during the build with a RUN instruction:

dockerfile
RUN echo "PATH is: $PATH" && which mytool

Multiple ENV Instructions and Layer Efficiency

Each ENV instruction creates a new image layer. If you need to set multiple environment variables, combine them in a single instruction to reduce layer count:

dockerfile
1# One layer instead of three
2ENV PATH="/opt/myapp/bin:${PATH}" \
3    APP_HOME="/opt/myapp" \
4    APP_ENV="production"

For PATH specifically, you typically only need one ENV PATH instruction. If you have multiple tools in different directories, concatenate them:

dockerfile
ENV PATH="/opt/tool-a/bin:/opt/tool-b/bin:${PATH}"

Common Pitfalls

Using RUN export instead of ENV. This is the most common mistake. The exported variable disappears after the RUN layer completes. Always use ENV for PATH changes that must persist.

Relative paths in PATH. Adding a relative path like ./bin to PATH can cause unpredictable behavior because it depends on the working directory at runtime. Always use absolute paths.

Forgetting ${PATH} when setting PATH. Writing ENV PATH="/opt/myapp/bin" without ${PATH} replaces the entire PATH, removing standard directories like /usr/bin. This breaks basic commands like ls, cat, and apt-get.

Overriding PATH at runtime. Running docker run -e PATH="/something" replaces the entire PATH set in the Dockerfile. If you need to extend PATH at runtime, reference the existing value: docker run -e PATH="/extra/dir:$PATH" your-image.

Multi-stage build confusion. PATH set in a builder stage does not carry over to the final stage. Each FROM instruction resets the environment to the new base image's defaults. You must set PATH again in the final stage.

Summary

Use ENV PATH="/your/dir:${PATH}" to update PATH in a Dockerfile. Always prepend rather than append when you want your binaries to take priority. Never use RUN export because it does not persist. Use absolute paths, always include ${PATH} to preserve existing directories, and remember that multi-stage builds require setting PATH in each stage. For debugging, use docker run --rm your-image env | grep PATH to verify the final value.


Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.