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:
- appsettings.json is pure JSON – no variable substitution happens
- Windows environment doesn’t use
.env
files by default - .NET Configuration reads JSON values literally
But when moving to Docker:
- Linux/Docker commonly uses
.env
files for configuration - .env file format follows shell variable substitution rules
- 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:
- Print the parsed password (temporarily, never in production logs):
Console.WriteLine($"Password length: {password.Length}"); // Original should be 21 chars for our example
- Test with a simple password first:
ConnectionStrings__AccountDataConnection=...Password=simplepassword123;...
If this works, you know it’s an escaping issue. - Use docker-compose config to see parsed values:
docker-compose config
This shows how Docker interpreted your.env
file. - 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!