A .NET developer's PC usually ends up with more database engines than projects: LocalDB from Visual
Studio, maybe a SQL Server Express service, a PostgreSQL that an installer left running, and the
PostgreSQL containers Docker makes cheap. This part looks at all of them on this PC, read-only where
they belong to someone else, finds out which one each connection string reaches, reproduces the
three connection errors that cost beginners the most time, and shows the tools that need nothing
installed: sqlcmd for LocalDB and the container's own psql and
pg_dump. Every output is quoted.
sqllocaldb info,sqllocaldb versions,sqllocaldb info MSSQLLocalDB, thensqlcmd -S "(localdb)\MSSQLLocalDB" -Q "select @version"Get-Service *SQL*andGet-Service postgresql*: which engines are services, and which is not- A console app with Microsoft.Data.SqlClient: connect to LocalDB, then try
Server=localhostand a database name that does not exist docker run -d --name ds-p4-pg -e POSTGRES_PASSWORD=... -p 15940:5432 -v ds-p4-pgdata:/var/lib/postgresql postgres:18- With Npgsql: connect on 15940, then on 5432, then with a wrong password; compare the three messages
docker exec ds-p4-pg pg_dump -U postgres -f /tmp/dump.sql postgresanddocker cpit out; try the redirect way and look at the first bytesdocker stop,start,rm -fand a newrunon the same volume
LocalDB: a process that runs as you
PS> sqllocaldb info
MSSQLLocalDB
PS> sqllocaldb versions
Microsoft SQL Server 2019 (15.0.4382.1)
Microsoft SQL Server 2022 (16.0.1210.3)
Microsoft SQL Server 2025 (17.0.4025.3)
Three LocalDB versions are installed on this PC and one automatic instance exists,
MSSQLLocalDB, running the 2022 build. It is not a service: Get-Service
lists none for it, and while a session is open a sqlservr.exe runs in the user's own
console session under the user's account, which matches the documentation's "Each instance runs as
the respective user in its own process." It starts when something connects and stops itself; this
PC's log recorded the stop about five minutes after the last connection closed:
The RANU instance is terminating in response to its internal time out. This is an informational message only. No user action is required.
The next sqlcmd query started it again in 0.76 seconds, with a new named pipe, which is
why connection strings say (localdb)\MSSQLLocalDB and never the pipe name. It has no TCP
listener at all, so nothing in a container, in WSL or on another PC can reach it. Two file facts
that save a search: the system databases sit under %LOCALAPPDATA%\Microsoft\Microsoft SQL
Server Local DB\Instances\MSSQLLocalDB, 54 MB here, and a CREATE DATABASE with no
file paths put its .mdf and .ldf in the root of the user profile, not in that
folder. sqlcmd, the ODBC variant 16.0 on this PC, is all the tooling LocalDB needs:
PS> sqlcmd -S "(localdb)\MSSQLLocalDB" -d DsP4Lab -W -Q "select * from patients"
id full_name date_of_birth phone
-- --------- ------------- -----
1 Maria Garcia 1988-03-14 +00-555-0101
2 David Chen 1975-11-02 +00-555-0102
3 Aisha Khan 1992-07-21 +00-555-0103
(3 rows affected)
-W trims the padding; without it full_name is a hundred characters wide. A
backup is one line too, BACKUP DATABASE DsP4Lab TO DISK = N'...' WITH INIT through
sqlcmd, written by the LocalDB process straight into a folder under your profile.
Express: the same engine as a service
MSSQL$SQLEXPRESS Running Automatic SQL Server (SQLEXPRESS)
SQLAgent$SQLEXPRESS Stopped Disabled SQL Server Agent (SQLEXPRESS)
SQLBrowser Stopped Disabled SQL Server Browser
This PC also has a SQL Server Express instance, and a read-only version query showed the same
build and the same "Express Edition" string as LocalDB; only SERVERPROPERTY('IsLocalDB')
tells them apart, 1 against 0. The difference is how they run: Express is an automatic service in
session 0, up all day, listening on a loopback port, with the Browser service that resolves instance
names disabled. The rule of thumb from the two: LocalDB for one developer on one machine, EF Core
samples and tests; a service instance when something other than your own session has to connect,
and then the TCP configuration this part did not touch.
Three connection strings that fail, and what each one says
A console app with Microsoft.Data.SqlClient 7.1.1 connected to LocalDB, to .\SQLEXPRESS and
to localhost\SQLEXPRESS in under 300 milliseconds each. Then the mistakes.
Server=localhost with no instance name asks for a default instance, and this PC has
none; after the full 15-second timeout:
Microsoft.Data.SqlClient.SqlException: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: Named Pipes Provider, error: 40 - Could not open a connection to SQL Server)
Error 40, not 26: the 26 in the Fixes
post came back here only for an instance name that does not exist, .\SQLEXPRESS01.
A database name that does not exist on LocalDB:
Microsoft.Data.SqlClient.SqlException: Cannot open database "DsP4Labs" requested by the login. The login failed.
Login failed for user '...'.
That one took ten seconds, and the same string with Connect Retry Count=0 failed in a
quarter of a second: the delay is the client retrying the login once. The
4060 post has
the fix. One more thing a reader sees when adding the package today: restore warnings NU1902 and
NU1903 about a vulnerable transitive package; the build succeeds, and the list changes over time.
Two PostgreSQL servers, and why the container gets another port
TCP 0.0.0.0:5432 0.0.0.0:0 LISTENING 8056
TCP [::]:5432 [::]:0 LISTENING 8056
A native PostgreSQL 18 service owns 5432 on every address of this PC, starts automatically, and was left alone. A container published on 5432 would collide with it, so the container gets 15940:
PS> docker run -d --name ds-p4-pg -e POSTGRES_PASSWORD=ds-p4-pass -p 15940:5432 -v ds-p4-pgdata:/var/lib/postgresql postgres:18
PS> docker exec ds-p4-pg psql -U postgres -c "select version()"
PostgreSQL 18.4 (Debian 18.4-1.pgdg13+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
Npgsql 10.0.3 connected on 15940 in 199 milliseconds. On 5432, with the container's password, it got the native server and failed:
Npgsql.PostgresException: 28P01: password authentication failed for user "postgres"
And on 15940 with a wrong password it printed the identical line. That is the point of the
section: a wrong port that lands on another PostgreSQL and a wrong password give exactly the same
exception, and the message cannot tell you which server answered. Check the port first; once
connected, select version() says which engine you reached, the container's naming Debian.
The container log holds the server side, with the pg_hba.conf rule that applied, and the
28P01 post
decodes it. One observation to keep in mind when reading such logs: a single failed open logged two
failures, because Npgsql's GSS encryption mode makes a second attempt; disabling it brought the
count to one.
psql and pg_dump without installing anything
psql and pg_dump are not on this PC's PATH, and they do not need to be: the
container has both. docker exec -i ds-p4-pg psql -U postgres with
Get-Content .\query.sql | in front runs a script, since PowerShell 5.1 refuses the
< operator. For a backup, the way that works is inside and out:
docker exec ds-p4-pg pg_dump -U postgres -f /tmp/ds-p4-dump.sql postgres
docker cp ds-p4-pg:/tmp/ds-p4-dump.sql .\ds-p4-dump.sql
The tempting docker exec ds-p4-pg pg_dump ... > dump.sql in a stock Windows PowerShell 5.1
writes UTF-16, first bytes FF FE, and the restore fails with
invalid byte sequence for encoding "UTF8": 0xff while psql still exits 0; the
Docker series' Part 5 met the
same trap. Finally, the volume: docker stop and start kept the rows, and
docker rm -f followed by a fresh docker run on the same named volume kept them
too, at the price of a recovery on the next start:
database system was not properly shut down; automatic recovery in progress
Stop a database container before removing it; the data is in the volume either way.
Which one for which project: LocalDB for anything SQL Server that only you run; PostgreSQL in Docker, one container per project on its own port, for anything PostgreSQL; a service instance only when another machine or container must reach it. Connection strings name the port and the instance explicitly, so that the error you get is about the server you meant.
Frequently asked
- What is the difference between LocalDB and SQL Server Express?
- The same engine and edition; on this PC both report build 16.0.1210.3 and Express Edition. LocalDB runs as a process under your own user account, starts on the first connection, stops itself after a few idle minutes, listens on a named pipe only and keeps its files in your profile. Express runs as a Windows service in session 0 all day and can listen on TCP. SERVERPROPERTY('IsLocalDB') tells them apart.
- Why does my .NET app get password authentication failed against PostgreSQL in Docker when the password is right?
- Because the connection string's port reaches a different PostgreSQL. On a PC with a native PostgreSQL on 5432, a container published on 15940 and a connection string that still says Port=5432 lands on the native server, and Npgsql reports the same 28P01 message it would for a wrong password. Set the published port, and compare select version() once connected.
- Do I need to install psql to use PostgreSQL in Docker?
- No. The container has psql and pg_dump: docker exec ds-p4-pg psql -U postgres -c for a query, Get-Content script.sql piped into docker exec -i for a script, and pg_dump -f inside the container followed by docker cp for a backup. Avoid redirecting pg_dump's output with > in Windows PowerShell 5.1, which writes UTF-16 that psql cannot read.
Next: Part 5, Git for Windows settings: line endings, long paths, credentials, the dubious-ownership check from WSL, and the identity a public repository should carry.