Containers Intermediate

Troubleshooting Docker Compose: Empty Environment Variables from .env on Ubuntu 20.04 LTS

Resolve Docker Compose .env file variables not loading correctly on Ubuntu 20.04 LTS, preventing services from accessing critical configuration. Learn common pitfalls and fixes.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Resolve Docker Compose .env file variables not loading correctly on Ubuntu 20.04 LTS, preventing services from accessing critical configuration. Learn common pitfalls and fixes.

This guide addresses a common issue where Docker Compose services fail to load environment variables defined in a .env file on Ubuntu 20.04 LTS systems. When this occurs, applications within your Docker containers often exhibit unexpected behavior, such as failing to connect to databases, misconfiguring API endpoints, or using default, insecure settings due to missing crucial environment variables. This can lead to application crashes, data integrity issues, or security vulnerabilities in your web applications or microservices.

Symptom & Error Signature

The primary symptom is that your Dockerized application behaves as if specific environment variables are unset or empty, even though you have them defined in your .env file. You might observe one or more of the following:

  • Application Logs: Errors within your container logs indicating missing configuration values, failed database connections, or API authentication failures.

    web_1 | ERROR: Database connection failed: PGHOST is not set.
    api_1 | WARN: API_KEY environment variable is empty. Using default.
    app_1 | ERROR: Configuration error: Required environment variable 'SERVICE_ENDPOINT' not found.
    
  • docker-compose config Output: When inspecting the merged configuration, the environment section for your service might show variables as explicitly unset, empty, or using fallback defaults, rather than the values from your .env file.

    # Example showing an empty variable or default being used
    $ docker-compose config
    # ... (truncated for brevity)
    services:
      web:
        environment:
          DB_HOST: '' # Should be 'mydbserver' from .env
          API_KEY: 'default_key' # Should be 'mysecretapikey'
          SERVICE_NAME: 'My App' # This loaded correctly
        image: myapp:latest
    # ...
    
  • Inside the Container: Directly inspecting the container's environment variables confirms the values are missing or incorrect.

    $ docker ps
    # ... find your container ID for the 'web' service ...
    $ docker exec <container_id> printenv | grep DB_HOST
    # (No output, or shows DB_HOST= if an empty string was passed)
    $ docker exec <container_id> printenv | grep API_KEY
    API_KEY=default_key # Not the one from .env
    

Root Cause Analysis

This issue typically stems from a misunderstanding or misconfiguration of how Docker Compose processes and prioritizes environment variables. The common underlying reasons include:

  1. Incorrect .env File Location: Docker Compose expects the .env file to be in the same directory as your docker-compose.yml file by default. If it's elsewhere, it won't be loaded.
  2. Incorrect .env File Naming: The file must be strictly named .env. Any variations like my.env, docker.env, or variables.env will not be automatically recognized.
  3. Syntax Errors in .env: The .env file follows a simple KEY=VALUE format. Common errors include:
    • Spaces around the = sign (e.g., KEY = VALUE).
    • Unquoted values containing spaces or special characters (e.g., SERVICE_NAME=My App).
    • Invisible characters (like Byte Order Marks – BOM) or trailing whitespace.
  4. File Permissions: The user executing docker-compose must have read permissions for the .env file.
  5. Environment Variable Precedence: Docker Compose has a strict hierarchy for loading variables. Variables defined in docker-compose.yml (under environment) or passed directly via the command line (docker-compose -e KEY=VALUE) take precedence over values in the .env file. Similarly, variables sourced via the env_file directive within docker-compose.yml can override the project-level .env file.
  6. Shell Expansion Issues: Variables defined in the .env file can sometimes be unexpectedly processed by the shell executing docker-compose, especially if they contain characters like $, ( or ). This is rare but can lead to unexpected values.
  7. Docker Compose Version Discrepancies: Older Docker Compose versions might have bugs or slightly different behavior regarding environment variable handling.
  8. Empty or Commented Lines: While generally harmless, leading or trailing empty lines or incorrectly commented lines can sometimes cause parser issues with specific Docker Compose versions.

Step-by-Step Resolution

Follow these steps to diagnose and resolve your Docker Compose .env variable loading issues.

1. Verify .env File Location and Naming

Ensure your .env file is correctly placed and named.

  1. Navigate to the directory containing your docker-compose.yml file.

  2. List its contents to confirm the .env file is present and correctly named.

    cd /path/to/your/docker-compose/project
    ls -la
    

    You should see an output similar to this, with .env listed:

    total 20
    drwxr-xr-x  3 user user 4096 Aug 26 10:00 .
    drwxr-xr-x 19 user user 4096 Aug 26 09:55 ..
    -rw-r--r--  1 user user  128 Aug 26 10:01 .env    # <--- This one!
    -rw-r--r--  1 user user  678 Aug 26 09:58 docker-compose.yml
    drwxr-xr-x  4 user user 4096 Aug 26 09:57 app
    

    Docker Compose only loads the .env file automatically if it resides in the same directory as the docker-compose.yml file, or in any parent directory up to the current working directory from where docker-compose is executed. Best practice is to keep it alongside docker-compose.yml.

2. Inspect .env File Content and Syntax

Incorrect formatting is a very common culprit. Review your .env file for proper KEY=VALUE syntax.

  1. View the contents of your .env file:

    cat .env
    
  2. Examine each line for adherence to these rules:

    • No spaces around =: VAR=value is correct; VAR = value is incorrect.
    • Comments: Start with #. VAR=value # This is a comment is fine; #VAR=value is commented out.
    • Quotes: Use quotes if your value contains spaces or special characters.
      • SERVICE_NAME="My Web Service" (correct)
      • SERVICE_NAME=My Web Service (incorrect, only My will be loaded)
    • No shell commands/expansion: Variables are treated as literal strings. Avoid things like VAR=$(pwd) or VAR=echo "hello"` unless explicitly handling shell interpolation outside of Docker Compose's direct processing.
    • No empty values: If KEY= is defined, it will result in an empty string. If you want a variable to be unset, omit it entirely from the .env file.

    Example of a correctly formatted .env file:

    # Database Configuration
    DB_HOST=my-database-server
    DB_PORT=5432
    DB_USER=appuser
    DB_PASSWORD=secretpassword123
    
    # Application Settings
    API_KEY=your_super_secret_api_key_123
    SERVICE_ENDPOINT=https://api.example.com/v1
    APP_DEBUG=true
    APP_NAME="My Awesome App"
    
  3. Check for Invisible Characters (BOM): On rare occasions, files saved from certain text editors might include a Byte Order Mark (BOM) at the beginning, which can confuse parsers.

    head -1 .env | hd
    

    If you see 0xef 0xbb 0xbf at the beginning, your file has a BOM. Remove it using a text editor that supports saving without BOM (e.g., VS Code, Notepad++), or by using sed:

    sed -i '1s/^xefxbbxbf//' .env
    

3. Check File Permissions

Ensure the .env file is readable by the user running docker-compose.

  1. Check current permissions:

    ls -l .env
    

    You should typically see something like -rw-r--r-- (permissions 644) or -rw-rw-r-- (permissions 664).

  2. If the permissions are too restrictive (e.g., ---r-----), change them:

    chmod 644 .env
    

    While chmod 777 .env would grant read access, it is a security risk as it makes the file writable by anyone. Stick to 644 or 664.

4. Understand Docker Compose Environment Variable Precedence

Docker Compose loads environment variables in a specific order, where later sources can override earlier ones. This is a frequent source of "variables not loaded" issues, where they are loaded but immediately overridden by another definition.

The precedence (highest to lowest):

  1. Command-line arguments: Variables passed directly when running docker-compose (e.g., DB_HOST=newhost docker-compose up).
  2. environment section in docker-compose.yml: Variables explicitly defined for a service.
  3. env_file directive in docker-compose.yml: Variables loaded from files specified using env_file for a service.
  4. Variables from the project .env file: The .env file in the same directory as docker-compose.yml.
  5. Environment variables from your shell: Variables already set in the shell where docker-compose is run.
  6. ENV instructions in the Dockerfile: Default environment variables set within the image itself.

Action:

  • Review docker-compose.yml: Check if any variables you expect from .env are also explicitly defined under the environment section for your service. If they are, the docker-compose.yml definition will take precedence.
    version: '3.8'
    services:
      web:
        image: myapp:latest
        environment:
          # This DB_HOST will override any DB_HOST in .env
          - DB_HOST=db-service-internal
          # This APP_DEBUG will override any APP_DEBUG in .env
          - APP_DEBUG=false
          # You can also reference .env variables and provide a default
          - API_KEY=${API_KEY:-default_fallback_key}
        # If you use env_file, ensure it's not conflicting or correctly structured
        # env_file:
        #   - ./another_specific.env
    
  • If you intend for variables from .env to be the primary source, remove conflicting environment declarations from docker-compose.yml or adjust them to use shell-like defaults (e.g., ${VAR:-default_value}) to allow .env to provide the primary value.

5. Validate Variable Loading with docker-compose config and printenv

Confirm what Docker Compose thinks it's configured to do and what the container actually has.

  1. Check docker-compose config: This command will parse your docker-compose.yml and .env files (and apply precedence rules) and print the effective configuration. Look for your service's environment section.

    docker-compose config
    

    Inspect the output carefully. If a variable from .env is not showing up correctly here, the issue is with the .env file itself or its precedence.

  2. Inspect running container: If docker-compose config shows the variables correctly but your application still fails, the issue might be within the container's runtime environment or how your application accesses them.

    # (If your services are not running)
    docker-compose up -d
    
    # Get the container ID or name of your problematic service
    docker ps
    
    # Execute printenv inside the container
    docker exec <container_id_or_name> printenv
    # Or to filter:
    docker exec <container_id_or_name> printenv | grep DB_HOST
    

    This will show you the exact environment variables available to processes inside that container. If the variables are still missing or incorrect here, double-check all preceding steps.

6. Update Docker Compose and Docker Engine

Outdated Docker Compose or Docker Engine versions can sometimes have bugs related to environment variable parsing or integration. Ubuntu 20.04 LTS repository versions can be quite old.

  1. Check your current versions:

    docker-compose --version
    docker --version
    
  2. Update Docker Compose (if using V1 via pip): If your Docker Compose version is below 1.27.x, consider upgrading.

    sudo pip uninstall docker-compose
    sudo apt update
    sudo apt install -y python3-pip
    sudo pip3 install docker-compose
    
  3. Install Docker Compose V2 (recommended plugin): Docker Compose V2 is now integrated as a plugin with the Docker CLI. It's recommended for modern Docker setups.

    For the latest and most stable Docker Engine and Docker Compose V2, it is highly recommended to install them directly from Docker's official repositories rather than Ubuntu's default apt repositories, which can often be out of date. Follow Docker's official installation guide for Ubuntu.

    # First, remove any existing Docker Compose V1 installations
    sudo rm /usr/local/bin/docker-compose
    sudo apt remove docker-compose
    
    # Install the Docker Compose plugin via apt (if Docker Engine from Docker repo is installed)
    sudo apt update
    sudo apt install docker-compose-plugin
    # Test:
    docker compose version # Note: 'docker compose' (no hyphen) for v2
    

    After updating, try rebuilding and restarting your services.

7. Restart Services

After making any changes to your .env file, docker-compose.yml, or Docker Compose installation, you must restart your Docker Compose services for the changes to take effect.

docker-compose down # Stop and remove containers, networks, and volumes
docker-compose up -d --build # Recreate and start services, optionally rebuilding images

Using docker-compose down ensures a clean slate, removing old container instances that might be holding onto stale environment variables. The --build flag is useful if your application uses a Dockerfile that might rely on build-time arguments sourced from the environment.

👨‍💻

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

🛡️

Our Production Verification Guarantee

Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.