| |

Docker 43 🐳 Scaling Compose Services, Dynamic Load Balancing, and Environment Variable Substitution

A Compose service is an abstract definition of a container. When the service is started, Compose creates one container for it. The scale option and the --scale flag create multiple containers for the same service, and Docker’s embedded DNS server load-balances the requests across them. The scaling is horizontal: more containers of the same service, each with the same configuration, behind the same service name. The caller uses the service name as the hostname, and Docker resolves it to one of the containers, rotating through them on each lookup.

The DNS round-robin is the load-balancing mechanism. When a service has multiple replicas, Docker’s DNS server returns all the IP addresses of the replicas, in a rotating order. The client’s resolver picks the first address, and the next lookup returns a different order. The rotation distributes the requests across the replicas, and it is transparent to the caller: the service name is the only thing the client needs to know. The mechanism is not a full load balancer — it does not check the health of the replicas, and it does not consider the current load — but it is sufficient for stateless services and for development.

The environment variable substitution is the mechanism that makes a Compose file portable. The ${VAR} syntax interpolates a host environment variable, and the ${VAR:-default} syntax provides a fallback. The .env file is read automatically, and its variables are available for interpolation. The --env-file flag loads a different file, and the environment and env_file keys in the service set the variables that are passed to the container. The precedence between the sources is defined by the specification, and understanding it is the key to avoiding surprises.

This chapter covers three areas. First, why scaling and load balancing exist — the horizontal-scaling problem and the DNS round-robin mechanism. Second, how to scale a service — the --scale flag, the scale key, the port conflict that scaling creates, and the stateless-service requirement. Third, how environment variable substitution works — the ${VAR} syntax, the .env file, the --env-file flag, the environment and env_file keys, and the precedence rules. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the scaling and substitution models.

Key point: docker compose up --scale web=3 creates three containers for the web service. The service name resolves to all the replica IPs, and Docker’s DNS server rotates the order, distributing the requests. Scaling requires the service to be stateless and the port mapping to be removed from the scaled service. Environment variable substitution uses ${VAR} and ${VAR:-default}, with the .env file read automatically, and the precedence is environment > env_file > host environment.


Why scaling and load balancing exist

The horizontal-scaling problem. A single container has a fixed amount of CPU and memory. When the load increases, the container cannot handle it, and the response time grows. The solution is to run multiple containers of the same service and distribute the requests across them. This is horizontal scaling, and it is the standard approach for stateless services. The alternative — vertical scaling — is to give the single container more resources, which is limited by the host’s capacity and requires a restart.

The load-balancing problem. When there are multiple replicas, the caller needs a way to reach one of them. The caller could be given a list of IP addresses, but the addresses change when the replicas are recreated, and the caller would need to implement the rotation. The service name is the stable identifier, and Docker’s DNS server resolves it to the replica IPs. The caller uses the service name, and Docker handles the resolution and the rotation.

The DNS round-robin mechanism. When a service has multiple replicas, Docker’s embedded DNS server returns all the replica IP addresses in a rotating order. The first lookup returns the addresses in one order, the second lookup returns them in a different order, and so on. The client’s resolver picks the first address, so consecutive lookups distribute the requests across the replicas. The mechanism is simple and transparent, and it works for any client that uses DNS to resolve the service name.

The stateless-service requirement. The DNS round-robin works only for stateless services. A stateful service — a service that stores session data in the container’s memory or in a local file — cannot be scaled, because the state is not shared across the replicas. A request that hits one replica and stores a session, and then hits another replica on the next request, will not find the session. The solution is to move the state to a shared store — a database, a cache, or a shared volume — so that all the replicas can access it.

The port-conflict problem. A scaled service cannot use a fixed host port. If the web service maps 8080:80 and there are three replicas, all three try to bind to host port 8080, and two of them fail. The fix is to remove the host port mapping from the scaled service and put it on a load balancer or a reverse proxy in front of the replicas. The proxy maps the host port to the service name, and Docker’s DNS resolves the service name to the replicas. The proxy distributes the requests across the replicas.

The trade-off. The DNS round-robin is a simple load-balancing mechanism, but it is not a full load balancer. It does not check the health of the replicas, so a request may be routed to a replica that is unhealthy. It does not consider the current load, so a slow replica receives the same share as a fast one. It does not support sticky sessions, so a client that needs to reach the same replica on every request cannot use it. For development and for simple stateless services, the round-robin is sufficient. For production, a real load balancer — nginx, HAProxy, Traefik, or a cloud load balancer — is the correct choice.


a. Scaling a service

A service is scaled with the --scale flag on the up command, or with the scale key in the Compose file. The flag overrides the key.

docker compose up -d --scale web=3

The command creates three containers for the web service. The containers are named myapp-web-1, myapp-web-2, and myapp-web-3, and they all use the same image and configuration. The service name web resolves to all three IP addresses, and Docker’s DNS server rotates the order.

The scale key in the Compose file sets the default number of replicas:

services:
  web:
    image: nginx:alpine
    scale: 3

The scale key is the equivalent of the --scale flag, and the flag overrides the key. The deploy.replicas key is the Swarm equivalent, and it is ignored by plain Docker Compose.

The port mapping is the constraint. A scaled service cannot bind a fixed host port, because the replicas would conflict. The port mapping must be removed from the scaled service and placed on a proxy:

services:
  proxy:
    image: nginx:alpine
    ports:
      - "8080:80"
    depends_on:
      - web

  web:
    image: myapp
    # no ports — the proxy handles the host port

The proxy service maps the host port and forwards the requests to the web service. The web service has no host port mapping, so the replicas do not conflict. The proxy’s configuration resolves the web service name, and Docker’s DNS server returns the replica IPs.

The docker compose up --scale web=3 command creates the proxy and the three replicas. The proxy is a single container, and the replicas are three containers. The proxy forwards the requests to the replicas, and the DNS round-robin distributes them.

The scaled service is stateless. The replicas share no state, and any replica can handle any request. If the service needs session state, the state must be stored in a shared store — a Redis cache, a database, or a shared volume — so that all the replicas can access it.


b. Environment variable substitution

The ${VAR} syntax interpolates a host environment variable. The value is substituted before the Compose file is parsed, so the interpolation applies to every key, not just environment.

services:
  api:
    image: myapi:${TAG:-latest}
    environment:
      DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/appdb
      LOG_LEVEL: ${LOG_LEVEL:-info}

The TAG variable is substituted in the image name. If it is unset, the latest default is used. The DB_PASSWORD variable is substituted in the DATABASE_URL. The LOG_LEVEL variable is substituted with the info default.

The substitution syntax has several forms:

SyntaxMeaning
${VAR}The value of VAR; empty if unset
${VAR:-default}The value of VAR, or default if unset or empty
${VAR-default}The value of VAR, or default if unset
${VAR:?error}The value of VAR, or an error if unset or empty
${VAR?error}The value of VAR, or an error if unset
${VAR:+replacement}replacement if VAR is set, empty otherwise
${VAR+replacement}replacement if VAR is set, empty otherwise

The :- and - forms differ in the treatment of an empty value. The :- form uses the default if the variable is unset or empty. The - form uses the default only if the variable is unset. The :? form fails with an error if the variable is unset or empty, which is useful for required variables.

The .env file is read automatically from the project directory. The variables in the file are available for interpolation, and they are not passed to the containers unless they are listed in the environment or env_file keys.

# .env
DB_PASSWORD=secret
LOG_LEVEL=debug
TAG=1.2.3

The --env-file flag loads a different file:

docker compose --env-file .env.production up -d

The environment key sets the variables that are passed to the container. The env_file key loads the variables from a file. The precedence between the sources is defined by the specification.

The precedence, from highest to lowest:

  1. The environment key in the service.
  2. The env_file key in the service.
  3. The host environment and the .env file interpolation.

The environment key wins over the env_file, and the env_file wins over the host environment. The .env file is used for interpolation, not for passing variables to the container, unless the variables are listed in the environment or env_file keys.


c. The scaling and substitution patterns

The scaling pattern is: a stateless service, no host port mapping, a proxy in front, and a shared store for the state.

services:
  proxy:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - web

  web:
    image: myapp
    environment:
      REDIS_URL: redis://cache:6379
    depends_on:
      - cache

  cache:
    image: redis:7-alpine

The proxy service handles the host port and forwards to the web service. The web service stores its session state in the cache service, so the replicas share it. The web service has no host port mapping, so the replicas do not conflict.

The nginx.conf for the proxy uses the service name as the upstream:

upstream web {
    server web:3000;
}

server {
    listen 80;
    location / {
        proxy_pass http://web;
    }
}

The web upstream resolves to the replica IPs, and nginx load-balances the requests across them. The server web:3000 line uses the service name, and Docker’s DNS server returns the replica IPs.

The substitution pattern is: the .env file for the defaults, the environment key for the overrides, and the :? form for the required variables.

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
      POSTGRES_DB: ${DB_NAME:-appdb}

The DB_PASSWORD variable is required, and the :? form fails with an error if it is unset. The DB_NAME variable has a default, and the :- form uses appdb if it is unset. The .env file provides the values, and the environment key passes them to the container.


Complete Example Session

# ============================================
# PART 1: A SCALABLE SERVICE
# ============================================

services:
  web:
    image: myapp
    environment:
      REDIS_URL: redis://cache:6379

  cache:
    image: redis:7-alpine

# The web service has no host port mapping.
# It can be scaled to multiple replicas.
# The cache is shared across the replicas.


# ============================================
# PART 2: THE PROXY IN FRONT
# ============================================

services:
  proxy:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - web

  web:
    image: myapp

  cache:
    image: redis:7-alpine

# The proxy handles the host port.
# The web service is behind the proxy.
# The proxy forwards to the web service name.


# ============================================
# PART 3: THE NGINX CONFIGURATION
# ============================================

# nginx.conf
# upstream web {
#     server web:3000;
# }
#
# server {
#     listen 80;
#     location / {
#         proxy_pass http://web;
#     }
# }

# The upstream resolves to the replica IPs.
# nginx load-balances the requests across them.


# ============================================
# PART 4: SCALING WITH THE FLAG
# ============================================

# $ docker compose up -d --scale web=3
#
# [+] Running 5/5
#  ✔ Container myapp-cache-1  Started
#  ✔ Container myapp-web-1    Started
#  ✔ Container myapp-web-2    Started
#  ✔ Container myapp-web-3    Started
#  ✔ Container myapp-proxy-1  Started


# ============================================
# PART 5: THE DNS ROUND-ROBIN
# ============================================

# $ docker compose exec proxy nslookup web
# Name:      web
# Address 1: 172.18.0.2  myapp-web-1
# Address 2: 172.18.0.3  myapp-web-2
# Address 3: 172.18.0.4  myapp-web-3
#
# The DNS server returns all three IPs.
# The order rotates on each lookup.


# ============================================
# PART 6: THE SCALE KEY
# ============================================

services:
  web:
    image: myapp
    scale: 3

# The scale key sets the default number of replicas.
# The --scale flag overrides it.


# ============================================
# PART 7: ENVIRONMENT VARIABLE SUBSTITUTION
# ============================================

services:
  api:
    image: myapi:${TAG:-latest}
    environment:
      DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/appdb
      LOG_LEVEL: ${LOG_LEVEL:-info}


# ============================================
# PART 8: THE .env FILE
# ============================================

# .env
# TAG=1.2.3
# DB_PASSWORD=secretpassword
# LOG_LEVEL=debug

# The .env file is read automatically.
# Its variables are available for interpolation.


# ============================================
# PART 9: THE REQUIRED VARIABLE
# ============================================

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
      POSTGRES_DB: ${DB_NAME:-appdb}

# The DB_PASSWORD variable is required.
# The :? form fails with an error if it is unset.
# The DB_NAME variable has a default.


# ============================================
# PART 10: THE COMPLETE STACK
# ============================================

name: scalable-app

services:
  proxy:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      web:
        condition: service_healthy
    networks:
      - frontend

  web:
    image: myapp:${TAG:-latest}
    environment:
      REDIS_URL: redis://cache:6379
      DATABASE_URL: postgres://app:${DB_PASSWORD:?DB_PASSWORD is required}@db:5432/appdb
      LOG_LEVEL: ${LOG_LEVEL:-info}
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s
    networks:
      - frontend
      - backend
    scale: 3

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required}
      POSTGRES_DB: ${DB_NAME:-appdb}
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 10s
    networks:
      - backend

  cache:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - backend

volumes:
  db-data:

networks:
  frontend:
  backend:
    internal: true

The ten parts show a scalable service, the proxy, the nginx configuration, scaling with the flag, the DNS round-robin, the scale key, environment variable substitution, the .env file, the required variable, and the complete stack.


Quick Reference

Scaling

Command / KeyEffect
docker compose up --scale web=3Create 3 replicas of web
scale: 3Default replica count in the file
deploy.replicasSwarm equivalent; ignored by Compose
No host portRequired for scaling
Proxy in frontHandles the host port
Stateless serviceRequired for scaling

DNS Round-Robin

AspectBehavior
Service nameResolves to all replica IPs
OrderRotates on each lookup
ClientPicks the first address
Health checkNot performed by DNS
Load awarenessNot performed by DNS

Substitution Syntax

SyntaxMeaning
${VAR}Value; empty if unset
${VAR:-default}Default if unset or empty
${VAR-default}Default if unset
${VAR:?error}Error if unset or empty
${VAR:+replacement}Replacement if set

Environment Precedence

PrioritySource
1 (highest)environment key in the service
2env_file key in the service
3 (lowest)Host environment and .env interpolation

The .env File

AspectBehavior
LocationProject directory
ReadAutomatically
PurposeInterpolation, not container variables
Override--env-file flag

Best Practices

✅ Do This:

# Remove the host port from a scaled service
web:
  image: myapp
  # no ports                                                              # ✅
# Put a proxy in front
proxy:
  ports:
    - "8080:80"                                                          # ✅
# Use a shared store for session state
environment:
  REDIS_URL: redis://cache:6379                                           # ✅
# Use the :? form for required variables
POSTGRES_PASSWORD: ${DB_PASSWORD:?required}                               # ✅
# Use the :- form for defaults
LOG_LEVEL: ${LOG_LEVEL:-info}                                             # ✅
# Use the .env file for environment-specific values
# .env: DB_PASSWORD=secret                                               # ✅

❌ Don’t Do This:

# Don't map a host port on a scaled service
web:
  ports:
    - "8080:80"  # conflict with replicas                                 # ❌
# Don't scale a stateful service without a shared store
web:
  scale: 3  # session state lost                                         # ❌
# Don't rely on the DNS round-robin for production
# Use a real load balancer                                               # ❌
# Don't hardcode secrets in the Compose file
POSTGRES_PASSWORD: secret                                                 # ❌
# Don't forget the .env file is for interpolation
# List variables in environment to pass them to the container            # ❌

Common Pitfalls

PitfallWhy It HappensFix
Port conflict on scaleFixed host portRemove the port, use a proxy
Session state lostStateful service scaledUse a shared store
DNS returns one IPService not scaledScale the service
Unhealthy replica receives requestsDNS does not check healthUse a load balancer with health checks
Variable not substitutedNot in .env or hostAdd the variable or a default
Required variable missingNo :? formAdd the :? form
Container variable not setNot in environment or env_fileAdd the variable

Real-World Examples

1. Scale a Service

docker compose up -d --scale web=3

2. The Scale Key

web:
  scale: 3

3. The Proxy

proxy:
  image: nginx
  ports:
    - "8080:80"

4. The Nginx Upstream

upstream web {
    server web:3000;
}

5. Substitution

image: myapi:${TAG:-latest}

6. Required Variable

POSTGRES_PASSWORD: ${DB_PASSWORD:?required}

7. The .env File

# .env
DB_PASSWORD=secret

8. The env_file Key

env_file:
  - .env

9. The Environment Key

environment:
  LOG_LEVEL: ${LOG_LEVEL:-info}

10. The Complete Stack

web:
  scale: 3
  environment:
    REDIS_URL: redis://cache:6379

Visual

The Scaling and Load-Balancing Model

┌──────────────────────────────────────────────────────────────┐
│  SCALING AND LOAD-BALANCING                                  │
│                                                              │
│  Client                                                      │
│    │                                                         │
│    ▼                                                         │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  proxy (nginx)                                       │    │
│  │  ports: 8080:80                                      │    │
│  │                                                       │    │
│  │  upstream web { server web:3000; }                   │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    │  resolves "web" to the replica IPs                      │
│    │                                                         │
│    ▼                                                         │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  web service (3 replicas)                            │    │
│  │                                                       │    │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐            │    │
│  │  │ web-1    │  │ web-2    │  │ web-3    │            │    │
│  │  │ 172.18.2 │  │ 172.18.3 │  │ 172.18.4 │            │    │
│  │  └──────────┘  └──────────┘  └──────────┘            │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    │  shared state                                            │
│    ▼                                                         │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  cache (redis)                                       │    │
│  │  shared session store                                │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  The proxy handles the host port.                            │
│  The replicas share the cache.                               │
│  The service name resolves to the replica IPs.               │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The DNS Round-Robin

┌──────────────────────────────────────────────────────────────┐
│  THE DNS ROUND-ROBIN                                         │
│                                                              │
│  Lookup 1: nslookup web                                      │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  web → 172.18.0.2 (web-1)                            │    │
│  │        172.18.0.3 (web-2)                            │    │
│  │        172.18.0.4 (web-3)                            │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  Lookup 2: nslookup web                                      │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  web → 172.18.0.3 (web-2)                            │    │
│  │        172.18.0.4 (web-3)                            │    │
│  │        172.18.0.2 (web-1)                            │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  Lookup 3: nslookup web                                      │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  web → 172.18.0.4 (web-3)                            │    │
│  │        172.18.0.2 (web-1)                            │    │
│  │        172.18.0.3 (web-2)                            │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  The order rotates on each lookup.                           │
│  The client picks the first address.                         │
│  The requests are distributed across the replicas.           │
│                                                              │
│  Note: the DNS server does not check health or load.         │
│  Use a real load balancer for production.                    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Environment Variable Substitution

┌──────────────────────────────────────────────────────────────┐
│  ENVIRONMENT VARIABLE SUBSTITUTION                           │
│                                                              │
│  Sources:                                                    │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  .env file (project directory)                       │    │
│  │    DB_PASSWORD=secret                                │    │
│  │    LOG_LEVEL=debug                                   │    │
│  │                                                       │    │
│  │  Host environment                                    │    │
│  │    export TAG=1.2.3                                  │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    ▼                                                         │
│  Interpolation in the Compose file:                          │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  image: myapi:${TAG:-latest}                         │    │
│  │    → myapi:1.2.3                                     │    │
│  │                                                       │    │
│  │  LOG_LEVEL: ${LOG_LEVEL:-info}                       │    │
│  │    → debug                                           │    │
│  │                                                       │    │
│  │  POSTGRES_PASSWORD: ${DB_PASSWORD:?required}         │    │
│  │    → secret                                          │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    ▼                                                         │
│  Passed to the container:                                    │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  environment:                                        │    │
│  │    LOG_LEVEL: debug                                  │    │
│  │    DATABASE_URL: postgres://app:secret@db:5432/appdb │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Environment Precedence

┌──────────────────────────────────────────────────────────────┐
│  ENVIRONMENT PRECEDENCE                                      │
│                                                              │
│  Highest:  environment key                                   │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  environment:                                        │    │
│  │    NODE_ENV: production                               │    │
│  │                                                       │    │
│  │  This value wins.                                    │    │
│  └──────────────────────────────────────────────────────┘    │
│    ▲                                                         │
│    │                                                         │
│  Middle:  env_file key                                       │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  env_file:                                           │    │
│  │    - .env                                             │    │
│  │                                                       │    │
│  │  This value is overridden by environment.            │    │
│  └──────────────────────────────────────────────────────┘    │
│    ▲                                                         │
│    │                                                         │
│  Lowest:  host environment and .env interpolation            │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  DATABASE_URL: ${DATABASE_URL}                       │    │
│  │                                                       │    │
│  │  The host variable or .env value is used.            │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
--scale web=3Creates 3 replicas
scale: 3Default replica count
DNS round-robinRotates the replica IPs
Host portMust be removed from a scaled service
ProxyHandles the host port
Shared storeRequired for session state
${VAR}Interpolation
${VAR:-default}Default if unset or empty
${VAR:?error}Error if unset or empty
.env fileRead automatically for interpolation

Key takeaways:

  • docker compose up --scale web=3 creates three containers for the web service. The service name resolves to all three IP addresses, and Docker’s DNS server rotates the order, distributing the requests across the replicas.
  • The DNS round-robin is a simple load-balancing mechanism. It returns all the replica IPs in a rotating order, and the client picks the first one. It does not check health or load, so a real load balancer is the correct choice for production.
  • A scaled service cannot map a fixed host port. The replicas would conflict. The port mapping must be removed from the scaled service and placed on a proxy or a load balancer in front of the replicas.
  • A scaled service must be stateless. The replicas share no state, so a session stored in one replica is not visible to the others. The state must be moved to a shared store — a cache, a database, or a shared volume.
  • The ${VAR} syntax interpolates a host environment variable. The ${VAR:-default} form provides a fallback, and the ${VAR:?error} form fails with an error if the variable is unset or empty.
  • The .env file is read automatically from the project directory. Its variables are available for interpolation, and they are not passed to the container unless they are listed in the environment or env_file keys.
  • The environment precedence is: environment > env_file > host environment. The environment key wins over the env_file, and the env_file wins over the host environment and the .env interpolation.
  • The --env-file flag loads a different file. It is useful for environment-specific configurations, such as .env.production or .env.staging.

Remember: Scaling a Compose service creates multiple replicas behind the same service name, and Docker’s DNS server rotates the replica IPs to distribute the requests. The scaled service must be stateless and must not map a fixed host port; a proxy handles the host port, and a shared store holds the session state. Environment variable substitution uses the ${VAR} syntax, with the .env file read automatically and the environment and env_file keys passing the variables to the container. The precedence is environment over env_file over the host environment. The result is a stack that scales horizontally, distributes the load, and configures itself from the environment.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!