Tips to help validate docker-compose.yml and .env files

1. Check for Invisible Characters (CLI)

  • Show Tabs and Ends of Lines:
    • Look for ^I:
      • This represents a Tab. YAML only allows spaces for indentation.
    • Look for ^M:
      • This indicates Windows-style line endings (CRLF), which can sometimes confuse older parsers on Linux.
cat -A docker-compose.yml
  • Search for Non-ASCII Characters:
    • If you copy-pasted the file, you might have “smart quotes” or hidden Unicode characters.
<code>grep -P '[^\x00-\x7f]' docker-compose.yml
  • Look for “Hidden Header”
    • Even if cat -A looks clean,
      • files moved from Windows to Unix often have a UTF-8 BOM (Byte Order Mark).
    • Docker’s Go-parser treats a file with a BOM as “not a key-value file” and ignores it.
    • Run this to create a 100% binary-clean version:
tail -c +1 .env > .env.fresh && mv .env.fresh .env

(If that doesn’t work, try tail -c +4 to specifically strip a 3-byte BOM).

tail -c +4 .env > .env.fresh && mv .env.fresh .env
  • Try this specific check:
    • If you see ef bb bf, you have a BOM that must be deleted.
<code>head -n 1 .env | od -t x1
  •  Check .env file permissions.
    You may need to run:
chmod 644 .env

2. Validate with Docker’s Built-in Tool

Use the config command to get a more detailed breakdown of the structural error.
Use the modern docker<strong> </strong>compose command to avoid the versioning issues discussed earlier: 

docker compose config

If the file is valid, this command will print the “resolved” version of your configuration.
If it’s invalid, it will often point to the specific key that is misaligned. 

  • Use “Shell Injection”
    • Use the shell to “pipe” the .env file into the command.
      • This bypasses Docker’s internal file parser entirely:
set -a; source .env; set +a; docker compose config
alias dcup='set -a; [ -f .env ] && source .env; set +a; docker compose up -d'
export $(grep -v '^#' .env | xargs) && docker compose config
export $(grep -v '^#' .env | x_args=() ; while read -r line; do [[ -z "$line" ]] && continue; x_args+=("$line"); done < .env; printf '%s\n' "${x_args[@]}") && docker compose config

3. Use an Online Validator

If you have access to a browser, copy and paste your content into a tool like YAMLlint or YAML Checker.
These tools provide high-contrast highlighting of exactly where a mapping (key-value pair) is placed where it shouldn’t be. 


1. The “Substitution” vs “Injection” Trap

There is a critical distinction in how Docker uses .env

  • Substitution (Default): 
    Docker reads .env to fill in the ${VAR} blanks inside your docker-compose.yml.

The Fix:
You must either reference the variables in the environment section or use the env_file directive: 

services:
  web:
    <em># Option A: Explicitly map them (Best for clarity)</em>
    environment:
      - SQLALCHEMY_DATABASE_URI=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
    
    <em># Option B: Inject the whole file into the container</em>
    env_file:
      - .env

2. Common “Silent” Failure Reasons

If your docker-compose.yml still shows empty values (check this with docker compose config), check these three things:

  • File Name: It must be exactly .env (no prefix, all lowercase) and in the same directory as your docker-compose.yml.
  • Syntax: Ensure there are no spaces around the equals sign (e.g., DB_USER=postgres ✅, DB_USER = postgres ❌).
  • Shell Overrides: If you have these variables exported in your current terminal session (e.g., export DB_USER=admin), the shell value will override whatever is in your .env file. GitHubGitHub +4

3. How to Debug it

Run this command in your terminal:

docker compose config

This will print out the “fully resolved” YAML.
If you see SQLALCHEMY_DATABASE_URI: postgresql://:@db:5432/ 
(with missing usernames/passwords), then Docker is failing to read the .env file.

Quick fix if it’s still being stubborn:
You can explicitly pass the file in the command:

docker compose --env-file .env up

Look for Trailing Spaces

look at this line:
LIC_REFRESH_INTERVAL=60 $

That space before the $ (the end-of-line marker) is a major red flag.
If your .env file has trailing spaces on lines
—or if it was saved with Windows-style line endings (CRLF) instead of Linux style (LF)
—Docker Compose V2 will often fail to parse the variables correctly,
resulting in the empty strings you see in your config output
(e.g., container_name: _db instead of licenses_db).

Clean the .env file

Run this command in your terminal to strip out any hidden carriage returns and trailing whitespace:

sed -i 's/[[:space:]]*$//' .env && sed -i 's/\r//' .env

After cleaning the .env file, verify it:

  1. Check Config again:
    docker compose config
    Look for container_name: licenses_db.
    If you see the word “licenses”, it worked!
  2. Explicitly point to the file (The “Fail-safe”):
    If it still shows empty values,
    your Docker version might be looking for the file in a different context.
    Force it with:
<code>docker compose --env-file .env config

Here is the industry-standard “Three-File” approach:

1. The Strategy

  • .env.example: (Check into Git) Contains keys but no secrets.
    Serves as a template for other devs.
  • .env.dev: (Git Ignore) Local database credentials, 
    FLASK_DEBUG=1, and SQL_ECHO=True.
  • .env.prod: (Git Ignore) Production secrets,
    restricted ports, and your specific IP strings.

2. Implementation

You can tell Docker which file to use without renaming them constantly.

Option A: The “Explicit” Command (Recommended)

Keep both files in your folder.
When you want to run one or the other,
use the --env-file flag:

For Development:

docker compose --env-file .env.dev up

For Production:

docker compose --env-file .env.prod up

Option B: The “Automatic” Way (Override)

Create a base docker-compose.yml and a docker-compose.override.yml.

  • docker-compose.yml: Defines the services.
  • docker-compose.override.yml:
    Docker automatically merges this file if it exists.
    You can put your env_file: .env.dev here.

3. Best Practices for Your Unraid Setup

Since you’re running this on Unraid, here is how to keep it clean:

  1. Strip the Whitespace:
    As we saw earlier, Unraid/Linux is picky.
    Always ensure your .env.prod is “Clean” (no trailing spaces).
  2. External DBs:
    In your .env.prod, you have 192.168.0.51:5417.
    In Dev, you’ll likely want db:5432 (the internal Docker name).
    Keeping these in separate files prevents you from accidentally overwriting your Prod DB with Dev migrations.
  3. The .gitignore Rule
    Never commit .env.dev or .env.prod to your feature/testing branch.
    Only commit .env.example.

Pro-Tip: The “Shell Injection”

If you want to keep it even simpler, you can set a shell variable before running the command:

<em># In your terminal</em>
ENV_STATE=prod docker compose up

And in your docker-compose.yml:

services:
  web:
    env_file:
      - .env.${ENV_STATE:-dev}

Leave a Reply