Docker
Dockerfile
ENV variable
unset environment variable
containerization

How to unset ENV in dockerfile?

System Design practice on Codemia

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

Practice system design

Introduction

Docker does not provide an UNSET instruction to remove environment variables set by ENV. Once set with ENV, a variable persists in all subsequent layers and in the running container. To work around this, you can set the variable to an empty string, use ARG instead of ENV for build-time-only variables, or use a shell command to unset it within a single RUN instruction. The approach depends on whether the variable is needed at build time, runtime, or both.

The Problem

dockerfile
1# Base image sets an ENV variable
2FROM node:20
3
4ENV DATABASE_URL=postgres://localhost/dev
5
6# There is NO way to do this:
7# UNSET DATABASE_URL   <-- not valid Dockerfile syntax

Any ENV variable is baked into the image metadata and available in every subsequent layer and at container runtime.

Method 1: Set to Empty String

The simplest approach — override the variable with an empty value:

dockerfile
1FROM node:20
2
3ENV DATABASE_URL=postgres://localhost/dev
4
5# "Unset" by setting to empty
6ENV DATABASE_URL=
7
8# Verify in the container:
9# echo $DATABASE_URL   -> (empty string)
10# env | grep DATABASE   -> DATABASE_URL=

The variable still exists (it shows up in env output), but its value is empty. Most applications treat an empty string the same as unset.

Method 2: Use ARG Instead of ENV

If the variable is only needed during the build (not at runtime), use ARG instead:

dockerfile
1FROM python:3.12
2
3# ARG is only available during build, not at runtime
4ARG PIP_INDEX_URL=https://private.pypi.org/simple/
5
6RUN pip install -r requirements.txt
7
8# PIP_INDEX_URL does NOT exist in the running container

ARG variables are automatically discarded after the build stage. They do not persist in the image metadata.

Converting ENV to ARG

dockerfile
1# Before: leaks the token into the image
2FROM node:20
3ENV NPM_TOKEN=secret123
4RUN npm install
5# NPM_TOKEN is visible in the running container!
6
7# After: token only exists during build
8FROM node:20
9ARG NPM_TOKEN
10RUN NPM_TOKEN=$NPM_TOKEN npm install
11# NPM_TOKEN is NOT in the running container

Method 3: Unset in a RUN Command

Use unset in a shell command, but it only affects that single RUN instruction:

dockerfile
1FROM ubuntu:22.04
2
3ENV MY_VAR=some_value
4
5# unset only applies within this RUN command
6RUN unset MY_VAR && echo "MY_VAR is: '$MY_VAR'"
7# Output: MY_VAR is: ''
8
9# MY_VAR is back in the next RUN (ENV persists across layers)
10RUN echo "MY_VAR is: '$MY_VAR'"
11# Output: MY_VAR is: 'some_value'

This is useful when a base image sets an ENV you want to suppress for a specific command:

dockerfile
1FROM some-base-image
2# Base sets JAVA_OPTS="-Xmx512m"
3
4# Override for just this command
5RUN unset JAVA_OPTS && java -jar build-tool.jar

Method 4: Multi-Stage Build

Use a multi-stage build to avoid carrying ENV variables from one stage to the next:

dockerfile
1# Stage 1: build with the secret
2FROM node:20 AS builder
3ENV NPM_TOKEN=secret123
4RUN npm install
5RUN npm run build
6
7# Stage 2: clean runtime image (no NPM_TOKEN)
8FROM node:20-slim
9COPY --from=builder /app/dist /app/dist
10COPY --from=builder /app/node_modules /app/node_modules
11CMD ["node", "/app/dist/index.js"]
12# NPM_TOKEN does not exist in this stage

Multi-stage builds are the cleanest way to ensure build-time secrets do not leak into the final image.

Method 5: Override at Runtime

Override or unset environment variables when running the container:

bash
1# Override with a different value
2docker run -e DATABASE_URL=postgres://prod-server/db myimage
3
4# Set to empty
5docker run -e DATABASE_URL= myimage
6
7# In docker-compose.yml
8services:
9  app:
10    image: myimage
11    environment:
12      - DATABASE_URL=  # empty override

Method 6: Use an Entrypoint Script

Create an entrypoint script that unsets variables before starting the application:

bash
1#!/bin/sh
2# entrypoint.sh
3
4# Unset build-time variables that should not be available at runtime
5unset BUILD_SECRET
6unset NPM_TOKEN
7
8# Execute the main command
9exec "$@"
dockerfile
1FROM node:20
2ENV BUILD_SECRET=abc123
3RUN npm install
4COPY entrypoint.sh /entrypoint.sh
5RUN chmod +x /entrypoint.sh
6ENTRYPOINT ["/entrypoint.sh"]
7CMD ["node", "index.js"]

The exec "$@" replaces the shell process with the main command, so the unset variables remain gone.

Inspecting ENV Variables in an Image

bash
1# See all ENV variables baked into the image
2docker inspect myimage --format '{{json .Config.Env}}'
3
4# Or run a shell in the container
5docker run --rm myimage env
6
7# Check image history for ENV instructions
8docker history myimage

Common Pitfalls

  • Thinking unset in RUN persists: RUN unset MY_VAR only affects that single shell session. The next RUN instruction starts a new shell where the ENV variable is restored. Each RUN creates a new layer with a fresh environment.
  • Secrets in ENV are visible in image metadata: docker inspect and docker history reveal all ENV values. Never put passwords, API keys, or tokens in ENV instructions. Use Docker BuildKit secrets (--mount=type=secret) or ARG with multi-stage builds instead.
  • ARG and ENV interaction: ARG values can be captured by ENV (ENV MY_VAR=$MY_ARG), which makes the value persist. If you use ARG for secrets, do not assign them to ENV.
  • Base image ENV variables: Parent images may set ENV variables you do not control. Use docker inspect base-image to see inherited variables. Override them with ENV VAR= or unset in an entrypoint script.
  • Empty string vs truly unset: Setting ENV MY_VAR= makes the variable exist with an empty value. Some applications distinguish between "variable exists but empty" and "variable does not exist" (e.g., checking if [ -z "${MY_VAR+x}" ]). Use an entrypoint script with unset for true removal.

Summary

  • Docker has no UNSET instruction — ENV variables persist in all subsequent layers and at runtime
  • Set to empty (ENV VAR=) for the simplest workaround
  • Use ARG instead of ENV for build-time-only variables
  • Use multi-stage builds to prevent build-time variables from leaking into the final image
  • Use an entrypoint script with unset for true runtime removal
  • Never store secrets in ENV — use Docker BuildKit secrets or ARG with multi-stage builds

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.