Bitcoin

Fixing “Login Failed” Errors When Dockerizing Your .NET App

Dockerizing a .NET application should be straightforward, right? Move your config to environment variables, build the container, and deploy. Except when your application suddenly can’t authenticate to the database, and you’re left staring at connection errors, wondering what went wrong.

What Worked on Windows

I had a .NET Core application running perfectly on Windows with this connection string in appsettings.json:

"ConnectionStrings": { "AccountDataConnection": "server=tcp:mycompany-prod-dbserver.database.windows.net;User ID=dbadmin;Password=MyP@ssw0rd$Example$123;Encrypt=true;database=MyApplicationDB" }

Everything worked beautifully. Local development, staging, production – no issues whatsoever.

The Problem: Dockerizing Breaks Authentication

Following best practices, I dockerized the application and moved all configuration from appsettings.json to a .env file for better security and environment management. My .env file looked like this:

ConnectionStrings__AccountDataConnection=server=tcp:mycompany-prod-dbserver.database.windows.net;User ID=dbadmin;Password=MyP@ssw0rd$Example$123;Encrypt=true;database=MyApplicationDB

Note: In .NET Core, the double underscore (__) syntax is used to represent nested configuration hierarchy (Linux-based configs). ConnectionStrings__AccountDataConnection maps to ConnectionStrings:AccountDataConnection in JSON (also Windows-based configs).

The Docker container built successfully, but when I ran it:

Microsoft.Data.SqlClient.SqlException: Login failed for user 'dbadmin'

Authentication failure. But the credentials were identical to what worked on Windows!

The Root Cause: Linux .env File Variable Substitution

Here’s what I didn’t realize: .env files in Linux/Docker treat the dollar sign ($) as a special character for variable substitution, just like Bash does.

When my Docker container (running on Linux) read this line:

ConnectionStrings__AccountDataConnection=...Password=MyP@ssw0rd$Example$123;...

It interpreted $Example and $123 as environment variable references. Since these variables didn’t exist, they evaluated to empty strings, effectively changing my password to:

MyP@ssw0rd

No wonder authentication failed!

The Solution: Escape Dollar Signs in .env Files

In .env files, you escape dollar signs by doubling them:

ConnectionStrings__AccountDataConnection=server=tcp:mycompany-prod-dbserver.database.windows.net;User ID=dbadmin;Password=MyP@ssw0rd$$Example$$123;Encrypt=true;database=MyApplicationDB

When the .env file parser reads $$, it interprets it as a literal $. This gives us back our original password: MyP@ssw0rd$Example$123.

Why This Only Happened During Dockerization

This issue didn’t exist in Windows because:

  1. appsettings.json is pure JSON – no variable substitution happens
  2. Windows environment doesn’t use .env files by default
  3. .NET Configuration reads JSON values literally

But when moving to Docker:

  1. Linux/Docker commonly uses .env files for configuration
  2. .env file format follows shell variable substitution rules
  3. Docker Compose and container runtimes parse .env files using these rules

A Related PowerShell Issue

Interestingly, I encountered a similar problem when using PowerShell to run bcp commands for database exports.

The PowerShell Problem

When running database export commands in PowerShell:

# This FAILS - PowerShell interprets $Example as a variable 
bcp "SELECT * FROM MyTable" queryout "data.csv" -S myserver -U dbadmin -P "MyP@ssw0rd$Example$123"

PowerShell treats double quotes specially – it expands variables inside them. So $Example gets interpreted as a PowerShell variable (which doesn’t exist), and your password gets corrupted.

The PowerShell Solution

Use single quotes instead of double quotes:

# This WORKS - single quotes treat everything literally 
bcp "SELECT * FROM MyTable" queryout "data.csv" -S myserver -U dbadmin -P 'MyP@ssw0rd$Example$123'

In PowerShell:

  • Double quotes (") = Variable expansion happens
  • Single quotes (') = Everything is literal, what you see is what you get

The lesson? Special characters in passwords behave differently across platforms and tools.

Characters That Need Escaping in .env Files

Beyond dollar signs, watch out for these characters in .env files:

  • $ – Variable substitution (escape as $$)
  • \ – Escape character (may need doubling as \\)
  • " and ' – Quote handling varies by implementation
  • # – Comment character (if at line start)
  • ` – Backtick in some shells

Best Practices for Passwords in Dockerized Applications

1. Document Your Escaping Rules

Create a README or migration guide that explicitly states:

## Password Escaping Rules

When moving credentials to .env files:

  • Replace each `$` with `$$`
  • Test authentication immediately after migration

2. Use Docker Secrets for Production

Instead of .env files in production, use Docker Secrets or your orchestration platform’s secret management:

# docker-compose.yml
services:
  app:
    secrets:
      - db_password

secrets:
  db_password:
    external: true

\

3. Validate Before Deployment

Create a simple test script that validates authentication before deploying:

#!/bin/bash
# test-connection.sh

docker-compose run --rm app dotnet test DbConnectionTest.dll

if [ $? -eq 0 ]; then
    echo "✓ Database authentication successful"
else
    echo "✗ Database authentication failed - check password escaping"
    exit 1
fi

4. Use Configuration Providers

Consider using environment-specific configuration providers:

// Program.cs 
builder.Configuration 
  .AddJsonFile("appsettings.json") 
  .AddEnvironmentVariables()  // Automatically reads env vars 
  .AddUserSecrets<Program>(); // For local development

5. Generate Passwords Without Special Characters

If you control password generation, consider using only alphanumeric characters and safe symbols for new passwords:

Safe: A-Z, a-z, 0-9, -, _ 
Problematic: $, `, \, ", ', #

\
This isn’t always possible with existing systems, but it’s worth considering for new deployments.

The Cross-Platform Password Matrix

| Context | Format | Example |
|—-|—-|—-|
| appsettings.json | Raw | Password=MyP@ssw0rd$Example$123 |
| .env file (Linux/Docker) | Escaped | Password=MyP@ssw0rd$$Example$$123 |
| PowerShell (double quotes) | Escaped or quoted | -P "MyP@ssw0rd$Example$123" |
| PowerShell (single quotes) | Raw | -P 'MyP@ssw0rd$Example$123' |
| Bash script (double quotes) | Escaped | PASSWORD="MyP@ssw0rd\$Example\$123" |
| Bash script (single quotes) | Raw | PASSWORD='MyP@ssw0rd$Example$123' |

Debugging Tips

If you’re facing similar authentication issues after Dockerization:

  1. Print the parsed password (temporarily, never in production logs): Console.WriteLine($"Password length: {password.Length}"); // Original should be 21 chars for our example
  2. Test with a simple password first: ConnectionStrings__AccountDataConnection=...Password=simplepassword123;... If this works, you know it’s an escaping issue.
  3. Use docker-compose config to see parsed values: docker-compose config This shows how Docker interpreted your .env file.
  4. Check the container’s environment variables: docker exec -it mycontainer env | grep ConnectionStrings

The Takeaway

Containerization introduces new layers where string interpretation can change. What works as a literal string in JSON configuration can become a variable substitution nightmare in .env files.

The same password may need different escaping depending on:

  • Operating system (Windows vs Linux)
  • Configuration format (JSON vs .env vs XML)
  • Shell context (PowerShell vs Bash)
  • Tooling (Docker Compose vs Kubernetes vs plain Docker)

When dockerizing applications, always test authentication immediately after migration. And remember: if your password has a $ in it, you probably need to double it to $$ in your .env file.

Don’t let a simple dollar sign derail your Docker deployment.


Have you encountered similar cross-platform configuration issues? Share your war stories in the comments!

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button