Fix: Docker Build ARG Not Available in RUN Commands
Quick Answer
How to fix Docker build ARG variables that are empty or undefined inside RUN commands — why ARG scope is limited, how ARG and ENV interact, multi-stage build ARG scoping, and secrets that shouldn't use ARG.
The Error
A Docker build ARG is passed but evaluates to empty inside a RUN command:
ARG API_URL
RUN echo "API URL is: $API_URL"
# Output: API URL is: ← empty!Or the build succeeds but the ARG value is not embedded in the image:
docker build --build-arg API_URL=https://api.example.com .
# Inside container: echo $API_URL → emptyOr in a multi-stage build, an ARG defined in one stage is not available in another:
ARG VERSION=1.0.0
FROM node:20 AS builder
RUN echo $VERSION # Empty — ARG before FROM is not automatically availableWhy This Happens
Docker’s ARG instruction has scope rules that are easy to get wrong:
ARGbeforeFROMis only available toFROM— anARGdeclared before the firstFROMinstruction can only be used inFROMitself (e.g., to set the base image version). It is not available inRUN,ENV, or other instructions.ARGscope ends at the stage boundary — in multi-stage builds, eachFROMstarts a new stage with a fresh scope. AnARGfrom stage 1 is not inherited by stage 2 unless redeclared.ARGvsENV—ARGvalues are only available at build time. They are not persisted in the final image or available at container runtime. UseENVto make a value available at runtime.- ARG with no default and no
--build-argvalue — if you declareARG MYVARwithout a default and don’t pass--build-arg MYVAR=value, the variable is empty.
Fix 1: Declare ARG in the Correct Scope
The scope rule: Every ARG must be declared within the stage that uses it:
# Wrong — ARG before FROM is only available to FROM, not to RUN
ARG NODE_VERSION=20
FROM node:${NODE_VERSION} # ✓ Works here
RUN echo $NODE_VERSION # ✗ Empty — out of scope
# Correct — redeclare ARG after FROM to make it available in the stage
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}
ARG NODE_VERSION # Redeclare (no default needed — inherits build-arg value)
RUN echo $NODE_VERSION # ✓ WorksFull example with multi-stage build:
# Global ARG — only for FROM instructions
ARG BASE_IMAGE=node:20-alpine
# Stage 1 — Builder
FROM ${BASE_IMAGE} AS builder
# Redeclare ARGs needed in this stage
ARG APP_VERSION=dev
ARG BUILD_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN echo "Building version: $APP_VERSION for env: $BUILD_ENV"
RUN npm run build
# Stage 2 — Production
FROM ${BASE_IMAGE} AS production
# ARGs do NOT carry over — redeclare what you need
ARG APP_VERSION=dev # Must redeclare here if used in this stage
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
ENV NODE_ENV=production
ENV APP_VERSION=${APP_VERSION} # Convert ARG to ENV for runtime availability
CMD ["node", "dist/server.js"]Fix 2: Pass ARG Values at Build Time
# Pass a single ARG
docker build --build-arg API_URL=https://api.example.com .
# Pass multiple ARGs
docker build \
--build-arg API_URL=https://api.example.com \
--build-arg APP_VERSION=1.2.3 \
--build-arg BUILD_ENV=production \
-t myapp:latest .
# With docker-compose# docker-compose.yml
services:
app:
build:
context: .
args:
API_URL: https://api.example.com
APP_VERSION: ${APP_VERSION:-dev} # Falls back to 'dev' if not set in host env
BUILD_ENV: productionVerify the ARG is received:
ARG API_URL
RUN echo "=== Build Args ===" && \
echo "API_URL=${API_URL}" && \
test -n "$API_URL" || (echo "ERROR: API_URL is empty" && exit 1)Fix 3: Convert ARG to ENV for Runtime Availability
ARG variables exist only during the build. If you need the value at container runtime (when the container runs), convert it to ENV:
FROM node:20-alpine
# Build-time only — empty at runtime
ARG API_URL
ARG APP_VERSION
# Convert to ENV — available at both build time AND runtime
ENV API_URL=${API_URL}
ENV APP_VERSION=${APP_VERSION:-unknown}
# Now available in RUN (build time)
RUN echo "Building with API_URL=${API_URL}"
# And available at container startup (runtime)
CMD ["node", "-e", "console.log(process.env.API_URL)"]Difference between ARG and ENV:
| Feature | ARG | ENV |
|---|---|---|
Available in RUN | ✓ (within scope) | ✓ |
| Available at runtime | ✗ | ✓ |
Appears in docker inspect | ✗ | ✓ |
| Overridable at build time | ✓ (via --build-arg) | ✗ |
| Overridable at run time | ✗ | ✓ (via -e flag) |
Warning:
ENVvalues set fromARGare baked into the image layers and visible indocker inspectand the image history. Do not useARG/ENVfor secrets (API keys, passwords). Use Docker secrets or a runtime secrets manager instead.
Fix 4: Fix ARG in Multi-Stage Builds
A common pattern — pass a build argument through multiple stages:
ARG REGISTRY=docker.io
ARG IMAGE_TAG=latest
# Stage 1 — Dependencies
FROM ${REGISTRY}/node:20-alpine AS deps
ARG NPM_TOKEN # Redeclare for this stage
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
COPY package*.json ./
RUN npm ci
RUN rm ~/.npmrc # Remove token file — not in final image
# Stage 2 — Builder
FROM deps AS builder
ARG APP_VERSION=dev # Redeclare for this stage
WORKDIR /app
COPY . .
ENV NEXT_PUBLIC_APP_VERSION=${APP_VERSION}
RUN npm run build
# Stage 3 — Runner
FROM ${REGISTRY}/node:20-alpine AS runner
ARG APP_VERSION=dev # Redeclare again if needed in runner stage
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
ENV NODE_ENV=production
ENV APP_VERSION=${APP_VERSION}
EXPOSE 3000
CMD ["npm", "start"]Build with all required args:
docker build \
--build-arg REGISTRY=my-registry.example.com \
--build-arg IMAGE_TAG=20-alpine \
--build-arg NPM_TOKEN=${NPM_TOKEN} \
--build-arg APP_VERSION=$(git describe --tags --always) \
--target runner \
-t myapp:latest .Fix 5: Avoid Using ARG for Secrets
ARG values appear in the Docker build cache and image history — they are not secure for secrets:
# This exposes the secret in docker history
docker build --build-arg DATABASE_PASSWORD=secret123 .
docker history myimage
# IMAGE CREATED BY
# ... /bin/sh -c #(nop) ARG DATABASE_PASSWORD=secret123 ← Visible!Use BuildKit secrets instead:
# syntax=docker/dockerfile:1
FROM node:20-alpine
# Mount a secret — not baked into any layer
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) \
npm config set //registry.npmjs.org/:_authToken="${NPM_TOKEN}" && \
npm ci && \
npm config delete //registry.npmjs.org/:_authToken# Pass secret at build time — not stored in image
DOCKER_BUILDKIT=1 docker build \
--secret id=npm_token,env=NPM_TOKEN \
.Or use .env file as secret:
DOCKER_BUILDKIT=1 docker build \
--secret id=envfile,src=.env.production \
.RUN --mount=type=secret,id=envfile \
export $(cat /run/secrets/envfile | xargs) && \
your-command-that-needs-env-varsFix 6: Use ARG for Conditional Logic
FROM node:20-alpine
ARG INSTALL_DEV_DEPS=false
COPY package*.json ./
# Conditional install based on ARG
RUN if [ "$INSTALL_DEV_DEPS" = "true" ]; then \
npm install; \
else \
npm ci --omit=dev; \
fi
# Build with dev deps
# docker build --build-arg INSTALL_DEV_DEPS=true .
# Build for production (default)
# docker build .ARG for platform-specific builds:
FROM --platform=${BUILDPLATFORM} node:20-alpine AS builder
ARG TARGETARCH
ARG TARGETPLATFORM
RUN echo "Building for platform: ${TARGETPLATFORM}, arch: ${TARGETARCH}"# Build for multiple platforms
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
-t myapp:latest .Still Not Working?
Enable BuildKit for better caching and secret support:
# Enable BuildKit
export DOCKER_BUILDKIT=1
docker build .
# Or set globally in Docker daemon
# /etc/docker/daemon.json: { "features": { "buildkit": true } }Print all available ARGs in your Dockerfile for debugging:
ARG API_URL
ARG APP_VERSION
ARG BUILD_ENV
RUN echo "=== All Build Args ===" && \
echo "API_URL=${API_URL:-NOT SET}" && \
echo "APP_VERSION=${APP_VERSION:-NOT SET}" && \
echo "BUILD_ENV=${BUILD_ENV:-NOT SET}"Check if the ARG is being shadowed by ENV. If an ENV instruction sets a variable with the same name as an ARG, the ENV value takes precedence for subsequent instructions:
ARG NODE_ENV=development
ENV NODE_ENV=production # Overrides ARG
RUN echo $NODE_ENV # 'production' — not 'development'For related Docker issues, see Fix: Docker Compose Environment Variables Not Loading and Fix: dotenv Not Loading.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AWS ECS Task Failed to Start
How to fix ECS tasks that fail to start — port binding errors, missing IAM permissions, Secrets Manager access, essential container exit codes, and health check failures.
Fix: Docker Multi-Stage Build COPY --from Failed
How to fix Docker multi-stage build errors — COPY --from stage not found, wrong stage name, artifacts not at expected path, and BuildKit caching issues.
Fix: AWS ECR Authentication Failed (docker login and push Errors)
How to fix AWS ECR authentication errors — no basic auth credentials, token expired, permission denied on push, and how to authenticate correctly from CI/CD pipelines and local development.
Fix: Docker Compose Environment Variables Not Loading from .env File
How to fix Docker Compose not loading environment variables from .env files — why variables are empty or undefined inside containers, the difference between env_file and variable substitution, and how to debug env var issues.