Devops

ARG, ENV & Build-Time Configuration

ARG or ENV? When to use which? What about .env files? Build-time secrets? This article untangles Docker's configuration mechanisms, showing you how to build flexible, secure, and maintainable images.

📋 At a Glance

AspectDetails
TopicARG, ENV, .env files, build-time secrets, configuration patterns
ComplexityIntermediate
PrerequisitesPart 3 (Build Process), Part 6 (Multi-Stage)
Key InsightARG is for build customization, ENV is for runtime behavior
Time to Master2 hours

🎯 What You'll Learn

  • ARG vs ENV - when to use each, scope rules, security implications
  • Build arguments - parameterizing builds safely
  • Environment variables - runtime configuration patterns
  • Secrets management - keeping sensitive data out of images
  • Configuration patterns - 12-factor app principles in Docker

🔥 Production Story: The Wrong Config in Production

A team deployed to production. Everything passed staging tests. Then orders started failing - the app was hitting staging payment APIs.

Investigation:
BASH(6 lines)
Code
Loading syntax highlighter...
The Dockerfile:
DOCKERFILE(5 lines)
Code
Loading syntax highlighter...
Root cause: The ARG default was staging. The CI pipeline built without overriding it, and the default got baked into the image.
Compounding the problem:
BASH(7 lines)
Code
Loading syntax highlighter...
The fix:
DOCKERFILE(5 lines)
Code
Loading syntax highlighter...
YAML(6 lines)
Code
Loading syntax highlighter...
Lesson: Build-time configuration should be for image variants, not environment-specific values. Runtime configuration should be environment-specific.

🧠 Mental Model: Configuration Layers

┌──────────────────────────────────────────────────────────────────────────┐
│                    DOCKER CONFIGURATION LAYERS                           │
│                                                                          │
│  BUILD TIME                              RUNTIME                         │
│  ═══════════                             ═══════                         │
│                                                                          │
│  ┌──────────────────┐                   ┌──────────────────────────────┐ │
│  │      ARG         │                   │      docker run -e           │ │
│  │  (--build-arg)   │                   │      docker-compose env      │ │
│  │                  │                   │      k8s ConfigMap/Secret    │ │
│  │  • Build only    │                   │                              │ │
│  │  • Not in image  │                   │  • Overrides ENV             │ │
│  │  • Visible in    │                   │  • Not in image              │ │
│  │    history!      │                   │  • Per-container             │ │
│  └────────┬─────────┘                   └──────────────┬───────────────┘ │
│           │                                            │                 │
│           │ Can set                                    │ Overrides       │
│           ▼                                            ▼                 │
│  ┌──────────────────────────────────────────────────────────────────────┐│
│  │                           ENV                                        ││
│  │                      (in Dockerfile)                                 ││
│  │                                                                      ││
│  │  • Baked into image                                                  ││
│  │  • Default values                                                    ││
│  │  • Available at build AND runtime                                    ││
│  │  • Overridable at runtime                                            ││
│  └──────────────────────────────────────────────────────────────────────┘│
│                                                                          │
│  Priority (highest wins):                                                │
│  1. docker run -e / compose environment                                  │
│  2. ENV in Dockerfile                                                    │
│  3. Base image ENV                                                       │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

🔬 Deep Dive

ARG: Build-Time Variables

ARG defines variables that can be passed at build time:

DOCKERFILE(11 lines)
Code
Loading syntax highlighter...
BASH(6 lines)
Code
Loading syntax highlighter...
ARG scope rules:
DOCKERFILE(14 lines)
Code
Loading syntax highlighter...
ARG security warning:
BASH(5 lines)
Code
Loading syntax highlighter...

ENV: Runtime Variables

ENV sets environment variables that persist in the image:

DOCKERFILE(11 lines)
Code
Loading syntax highlighter...
ENV characteristics:
  • Persists in image metadata
  • Available during build (after definition)
  • Available at runtime
  • Overridable with docker run -e
Combining ARG and ENV:
DOCKERFILE(8 lines)
Code
Loading syntax highlighter...

The .env File Confusion

Docker has multiple .env file contexts:

1. Docker Compose .env (for compose variables):
BASH(3 lines)
Code
Loading syntax highlighter...
YAML(7 lines)
Code
Loading syntax highlighter...
2. Container .env (for application):
YAML(5 lines)
Code
Loading syntax highlighter...
BASH(3 lines)
Code
Loading syntax highlighter...
3. Build-time .env (NOT a thing!):
BASH(9 lines)
Code
Loading syntax highlighter...

Configuration Patterns

Pattern 1: Build-time version embedding
DOCKERFILE(9 lines)
Code
Loading syntax highlighter...
BASH(5 lines)
Code
Loading syntax highlighter...
Pattern 2: Environment-agnostic images
DOCKERFILE(13 lines)
Code
Loading syntax highlighter...
YAML(13 lines)
Code
Loading syntax highlighter...
Pattern 3: Feature flags via ARG
DOCKERFILE(11 lines)
Code
Loading syntax highlighter...
BASH(5 lines)
Code
Loading syntax highlighter...

Secrets Management

Never do this:
DOCKERFILE(7 lines)
Code
Loading syntax highlighter...
BuildKit secrets (correct):
DOCKERFILE(13 lines)
Code
Loading syntax highlighter...
BASH
Code
Loading syntax highlighter...
Runtime secrets:
YAML(11 lines)
Code
Loading syntax highlighter...
JAVASCRIPT(5 lines)
Code
Loading syntax highlighter...

Multi-Stage ARG Propagation

DOCKERFILE(16 lines)
Code
Loading syntax highlighter...
Pattern: Passing values between stages
DOCKERFILE(12 lines)
Code
Loading syntax highlighter...

Debugging Configuration

BASH(11 lines)
Code
Loading syntax highlighter...

⚠️ Common Mistakes

Mistake 1: Using ARG for Secrets

DOCKERFILE(7 lines)
Code
Loading syntax highlighter...

Mistake 2: Baking Environment URLs

DOCKERFILE(8 lines)
Code
Loading syntax highlighter...

Mistake 3: Not Understanding ARG Scope

DOCKERFILE(9 lines)
Code
Loading syntax highlighter...

🐛 Debug This: The Disappearing Variable

A developer reports: "My ARG works in the first stage but disappears in the final stage!"

DOCKERFILE(10 lines)
Code
Loading syntax highlighter...
Why is APP_VERSION empty in the second stage?

✅ Solution:
ARG values do not persist across stages. Each FROM starts a new stage with a clean ARG scope.
The problem:
  1. ARG APP_VERSION=1.0.0 is defined globally (before first FROM)
  2. First stage implicitly has access to global ARGs
  3. Second stage starts fresh - no ARGs defined
  4. ${APP_VERSION} is empty because it wasn't redeclared
Fix:
DOCKERFILE(13 lines)
Code
Loading syntax highlighter...
Alternative: Use file to pass between stages:
DOCKERFILE(7 lines)
Code
Loading syntax highlighter...
Key lesson: Treat each stage as independent. Explicitly redeclare any ARG you need.

💻 Exercises

Exercise 1: ARG vs ENV Behavior

⭐ Difficulty: Easy | ⏱️ Time: 15 minutes

DOCKERFILE(28 lines)
Code
Loading syntax highlighter...

Exercise 2: Multi-Stage ARG Propagation

⭐⭐ Difficulty: Medium | ⏱️ Time: 20 minutes

DOCKERFILE(18 lines)
Code
Loading syntax highlighter...
BASH(8 lines)
Code
Loading syntax highlighter...

Exercise 3: BuildKit Secrets

⭐⭐ Difficulty: Medium | ⏱️ Time: 20 minutes

BASH(31 lines)
Code
Loading syntax highlighter...

Exercise 4: Environment-Agnostic Image

⭐⭐⭐ Difficulty: Hard | ⏱️ Time: 25 minutes

Create a Dockerfile and compose files for an app that:

  1. Has ONE image used for all environments
  2. Configuration differs by environment via compose files
  3. Defaults work for local development
BASH(19 lines)
Code
Loading syntax highlighter...

Exercise 5: Complete Configuration Strategy

⭐⭐⭐⭐ Difficulty: Expert | ⏱️ Time: 30 minutes

Build a complete configuration system:

Requirements:
1. Build-time configuration:
   - VERSION (embedded in image)
   - BUILD_DATE (metadata)
   - FEATURES (enable/disable features)

2. Runtime configuration:
   - DATABASE_URL (environment-specific)
   - API_KEY (secret, not in image)
   - LOG_LEVEL (overridable)

3. Multi-stage build preserving ARGs

4. Proper secrets handling

Deliverables:

  • Dockerfile with proper ARG/ENV usage
  • docker-compose.yml for development
  • docker-compose.prod.yml for production
  • Secrets file handling
  • Build script with all arguments

🎤 Senior-Level Interview Questions

Q1: Explain the difference between ARG and ENV in Docker.

Strong Answer:

"ARG and ENV serve different purposes in the Docker build lifecycle:

ARG (Build-time):
  • Available only during build
  • Passed via --build-arg
  • Not persisted in the final image (except in history!)
  • Resets at each FROM statement
  • Can have defaults: ARG VERSION=1.0
ENV (Runtime):
  • Persisted in the image
  • Available at build time (after declaration) AND runtime
  • Can be overridden with docker run -e
  • Doesn't reset across stages
  • Truly embedded in image
Common pattern - combining both:
DOCKERFILE(2 lines)
Code
Loading syntax highlighter...

This gives you:

  • Build-time flexibility via ARG
  • Runtime accessibility via ENV
  • Overridability at both stages
Security consideration: ARGs are visible in docker history. Never use ARG for secrets. Even with --squash, the values can be extracted. Use BuildKit --mount=type=secret instead."

Q2: How do you handle secrets at build time?

Strong Answer:

"There are several approaches with different security levels:

Level 1 - Multi-stage isolation (okay):
DOCKERFILE(7 lines)
Code
Loading syntax highlighter...

Token is in builder history but not final image.

Level 2 - BuildKit secrets (recommended):
DOCKERFILE(3 lines)
Code
Loading syntax highlighter...

Secret available only during that RUN, not in any layer.

Level 3 - Don't use secrets at build time:
  • Fetch from secret manager at runtime
  • Use credential helpers
  • Proxy through secure service
For SSH:
DOCKERFILE
Code
Loading syntax highlighter...
Never do:
  • ARG for secrets
  • COPY secret files
  • ENV with secrets
  • Echo/cat secrets in RUN

The key principle: secrets should exist only during specific RUN commands and never be written to any layer."

Q3: How do you manage configuration across multiple environments?

Strong Answer:

"I follow the 12-factor app principle - one image, environment-specific configuration at runtime:

Build (environment-agnostic):
DOCKERFILE(3 lines)
Code
Loading syntax highlighter...
Runtime (compose or orchestrator):
YAML(20 lines)
Code
Loading syntax highlighter...
Deployment:
BASH(5 lines)
Code
Loading syntax highlighter...
For Kubernetes:
  • ConfigMaps for non-sensitive config
  • Secrets for sensitive values
  • Same image, different config per namespace
What goes where:
  • Build time (ARG): Version, build flags, feature toggles for compilation
  • Image (ENV): Safe defaults, NODE_ENV=production
  • Runtime: URLs, credentials, environment-specific values

This ensures: same image in staging and production, only configuration differs."

Q4: What happens when you reference an undefined ARG or ENV?

Strong Answer:

"The behavior differs between ARG and ENV:

Undefined ARG:
  • Silently empty string
  • No build failure
  • Can cause subtle bugs
DOCKERFILE(2 lines)
Code
Loading syntax highlighter...
Undefined ENV:
  • Also empty string
  • Silent unless explicitly checked
Best practices:
  1. Require ARGs when necessary:
DOCKERFILE(2 lines)
Code
Loading syntax highlighter...
  1. Provide safe defaults:
DOCKERFILE(2 lines)
Code
Loading syntax highlighter...
  1. Fail fast for required values:
DOCKERFILE(5 lines)
Code
Loading syntax highlighter...
  1. Document required variables:
DOCKERFILE(5 lines)
Code
Loading syntax highlighter...

I prefer explicit failures over silent empty values. Configuration bugs are easier to debug when they fail immediately."

Q5: How do ARGs interact with Docker's build cache?

Strong Answer:

"ARG values are part of the cache key for layers that use them:

Cache behavior:
DOCKERFILE(3 lines)
Code
Loading syntax highlighter...

If VERSION changes:

  • First RUN: cache miss (uses VERSION)
  • Second RUN: also cache miss (parent changed)

If VERSION stays same:

  • Both RUNs: cache hit
Important nuance:
DOCKERFILE(4 lines)
Code
Loading syntax highlighter...
Implications:
  1. Put ARGs as late as possible to maximize cache
  2. Separate build-affecting ARGs from metadata ARGs
  3. Don't use timestamps in ARGs unless you want cache bust
Example optimization:
DOCKERFILE(13 lines)
Code
Loading syntax highlighter...

For cache-busting deliberately:

DOCKERFILE(2 lines)
Code
Loading syntax highlighter...
Build with: docker build --build-arg CACHEBUST=$(date +%s)"

📝 Summary & Key Takeaways

ARG vs ENV Quick Reference

AspectARGENV
ScopeBuild onlyBuild + Runtime
PersistedNo (but in history!)Yes
Override--build-argdocker run -e
Secrets❌ Never❌ Never
Resets at FROMYesNo

Configuration Hierarchy

Runtime -e / compose environment  (highest priority)
        ↓
ENV in Dockerfile
        ↓
Base image ENV
        ↓
Application defaults  (lowest priority)

Best Practices

  1. ARG for build variants - versions, feature flags
  2. ENV for runtime defaults - safe, overridable values
  3. Never bake secrets - Use BuildKit mounts or runtime injection
  4. One image, many configs - Environment differences at runtime
  5. Explicit over implicit - Fail fast on missing required values

📋 Quick Reference

Dockerfile Syntax

DOCKERFILE(16 lines)
Code
Loading syntax highlighter...

Build Commands

BASH(10 lines)
Code
Loading syntax highlighter...

Runtime Configuration

BASH(8 lines)
Code
Loading syntax highlighter...

📅 Review Schedule

DayTaskTime
Day 1Review ARG vs ENV table5 min
Day 3Do Exercise 1 (ARG vs ENV behavior)15 min
Day 7Implement BuildKit secrets in a project20 min
Day 14Audit project for configuration anti-patterns20 min
Day 30Create environment-agnostic image for real project30 min

📚 Series Navigation

PreviousCurrentNext
Part 7: Base Image SelectionPart 8: Build ConfigurationPart 9: Resource Management
Docker Compendium Series: