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: