Docker
Docker-compose
Conditional Statements
Configuration Management
DevOps

Docker-compose, conditional statements? e.g. add volume only if condition

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Docker Compose does not support native conditional statements (if/else) in docker-compose.yml files. However, there are several workarounds to achieve conditional behavior: environment variable substitution with default values, multiple compose files, profiles (Compose V2), and external templating tools. These techniques let you conditionally add volumes, ports, services, or other configuration based on the environment.

Method 1: Environment Variable Substitution

Docker Compose supports variable substitution with default values using ${VAR:-default} syntax:

yaml
1services:
2  app:
3    image: myapp:latest
4    volumes:
5      - "${VOLUME_PATH:-/dev/null}:/app/data"
6    ports:
7      - "${APP_PORT:-8080}:8080"
8    environment:
9      - "DEBUG=${DEBUG_MODE:-false}"
bash
1# With volume mounted
2VOLUME_PATH=./data docker compose up
3
4# Without volume (maps to /dev/null — effectively no volume)
5docker compose up

The /dev/null trick works for "disabling" a volume, but it is a hack — the mount still exists, it just points to nothing.

Method 2: Multiple Compose Files

Docker Compose can merge multiple files. Use a base file and environment-specific overrides:

docker-compose.yml (base)

yaml
1services:
2  app:
3    image: myapp:latest
4    ports:
5      - "8080:8080"
6    environment:
7      - NODE_ENV=production

docker-compose.dev.yml (development overrides)

yaml
1services:
2  app:
3    volumes:
4      - ./src:/app/src        # Hot reload source code
5      - ./logs:/app/logs       # Local log directory
6    environment:
7      - NODE_ENV=development
8      - DEBUG=true
9    ports:
10      - "9229:9229"           # Debugger port

docker-compose.prod.yml (production overrides)

yaml
1services:
2  app:
3    restart: always
4    deploy:
5      replicas: 3
6      resources:
7        limits:
8          cpus: '2.0'
9          memory: 1G
bash
1# Development
2docker compose -f docker-compose.yml -f docker-compose.dev.yml up
3
4# Production
5docker compose -f docker-compose.yml -f docker-compose.prod.yml up

The second file merges into the first. Properties in the override file take precedence.

Method 3: Profiles (Compose V2)

Compose profiles let you conditionally enable services:

yaml
1services:
2  app:
3    image: myapp:latest
4    ports:
5      - "8080:8080"
6
7  debug-tools:
8    image: nicolaka/netshoot
9    profiles:
10      - debug
11    network_mode: "service:app"
12
13  monitoring:
14    image: prometheus
15    profiles:
16      - monitoring
17    ports:
18      - "9090:9090"
19
20  db-admin:
21    image: adminer
22    profiles:
23      - debug
24      - monitoring
25    ports:
26      - "8081:8080"
bash
1# Only the app service starts (no profile specified)
2docker compose up
3
4# Start app + debug tools + db-admin
5docker compose --profile debug up
6
7# Start app + monitoring + db-admin
8docker compose --profile monitoring up
9
10# Start everything
11docker compose --profile debug --profile monitoring up

Services without a profiles key always start. Services with profiles only start when their profile is active.

Method 4: Using .env Files

Create different .env files for each environment:

.env.dev

env
1COMPOSE_FILE=docker-compose.yml:docker-compose.dev.yml
2APP_PORT=3000
3LOG_LEVEL=debug
4MOUNT_SOURCE=./src:/app/src

.env.prod

env
1COMPOSE_FILE=docker-compose.yml:docker-compose.prod.yml
2APP_PORT=80
3LOG_LEVEL=warn
4MOUNT_SOURCE=/dev/null:/app/src
bash
1# Use dev config
2docker compose --env-file .env.dev up
3
4# Use prod config
5docker compose --env-file .env.prod up

Method 5: YAML Anchors and Extensions

Use YAML anchors to define reusable blocks:

yaml
1x-common-env: &common-env
2  LOG_LEVEL: info
3  TZ: UTC
4
5x-dev-volumes: &dev-volumes
6  - ./src:/app/src
7  - ./config:/app/config
8
9services:
10  app:
11    image: myapp:latest
12    environment:
13      <<: *common-env
14      NODE_ENV: ${NODE_ENV:-production}
15    volumes: ${DEV_VOLUMES:-[]}

Method 6: Docker Compose with Bash Script

Wrap docker compose in a script for complex conditional logic:

bash
1#!/bin/bash
2# deploy.sh
3
4ENV=${1:-dev}
5COMPOSE_FILES="-f docker-compose.yml"
6
7if [ "$ENV" = "dev" ]; then
8    COMPOSE_FILES="$COMPOSE_FILES -f docker-compose.dev.yml"
9    export MOUNT_VOLUMES=true
10fi
11
12if [ "$ENV" = "prod" ]; then
13    COMPOSE_FILES="$COMPOSE_FILES -f docker-compose.prod.yml"
14    export MOUNT_VOLUMES=false
15fi
16
17if [ "$ENABLE_MONITORING" = "true" ]; then
18    COMPOSE_FILES="$COMPOSE_FILES -f docker-compose.monitoring.yml"
19fi
20
21docker compose $COMPOSE_FILES up -d
bash
./deploy.sh dev
./deploy.sh prod
ENABLE_MONITORING=true ./deploy.sh prod

Method 7: Conditional Volume with Empty Variable

A cleaner way to conditionally add volumes using variable substitution:

yaml
1services:
2  app:
3    image: myapp:latest
4    volumes:
5      - app-data:/app/data
6      - ${EXTRA_VOLUME:-/dev/null:/dev/null}
bash
1# With extra volume
2EXTRA_VOLUME=./uploads:/app/uploads docker compose up
3
4# Without extra volume (maps /dev/null to /dev/null — essentially a no-op)
5docker compose up

Comparison of Approaches

MethodComplexityFlexibilityBest For
Env var substitutionLowLimitedSimple value changes
Multiple compose filesMediumHighEnvironment-specific configs
ProfilesLowMediumOptional services
.env filesLowMediumPer-environment defaults
Bash wrapperHighHighestComplex conditional logic

Common Pitfalls

  • Variable must be defined: ${VAR} without a default raises an error if VAR is unset. Always use ${VAR:-default} to provide a fallback.
  • Compose file merge order matters: The last file wins when properties conflict. Put overrides in the later file: docker compose -f base.yml -f override.yml up.
  • Profiles and depends_on: If a profiled service depends on another profiled service, both profiles must be active. Otherwise, the dependency is not started and the service fails.
  • Empty volumes list: You cannot use a variable to conditionally include or exclude an entire YAML key (like volumes:). The key must exist — you can only control its values.
  • Docker Compose V1 vs V2: Profiles are only available in Docker Compose V2 (docker compose, not docker-compose). The name property and some variable substitution features are also V2-only.

Summary

  • Docker Compose has no native if/else — use environment variables, multiple files, or profiles
  • Use ${VAR:-default} for simple conditional values
  • Use multiple compose files (-f base.yml -f override.yml) for environment-specific configuration
  • Use profiles to conditionally enable optional services like debugging tools or monitoring
  • For complex logic, wrap docker compose in a shell script

Course illustration
Course illustration

All Rights Reserved.