initdb: error: directory "/var/lib/postgresql/data" exists but is not empty
— the postgres container found something already in its data directory, refused to create
a database on top of it and exited with code 1. Point PGDATA at a
subdirectory of the mount, or mount an empty named volume, and it starts.
With docker run that is one more option,
-e PGDATA=/var/lib/postgresql/data/pgdata; in a compose file the same variable
goes under environment. Before you empty the folder instead, look at what is in
it: one of the two causes below is a real database.
The error
PS> docker run -d --name fix5-pg-ne -e POSTGRES_PASSWORD=fix5 -p 127.0.0.1:15483:5432 -v ${PWD}\pgdata:/var/lib/postgresql/data postgres:16
PS> docker logs fix5-pg-ne
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".
Data page checksums are disabled.
initdb: error: directory "/var/lib/postgresql/data" exists but is not empty
initdb: detail: It contains a dot-prefixed/invisible file, perhaps due to it being a mount point.
initdb: hint: Using a mount point directly as the data directory is not recommended.
Create a subdirectory under the mount point.
Why it happens
initdb only creates a cluster in a directory that is missing or empty, so it will not
write over anything. The image's entrypoint runs initdb whenever the data directory has no
PG_VERSION file. For postgres:16 that directory is
/var/lib/postgresql/data, the mount point itself, so anything the mount brings
along is in the way. Here it was a 0-byte .gitkeep in the Windows folder, the
kind of file that keeps an empty folder in Git, and initdb's detail line spotted it.
The second cause reproduced was a volume created by postgres:18 and then started
with postgres:16. The 18 image keeps its cluster in an 18/docker
folder inside the volume, so 16 finds that folder and no PG_VERSION, and prints the
same error, with a hint to empty the directory instead of the detail line. Do not empty it:
that folder is the 18 database. Moving the mount up to /var/lib/postgresql is no
better; postgres:16 then quietly created a new, empty cluster beside it, and the
old table answered relation "note" does not exist.
The fix
PS> docker run -d --name fix5-pg-ne -e POSTGRES_PASSWORD=fix5 -e PGDATA=/var/lib/postgresql/data/pgdata -p 127.0.0.1:15483:5432 -v ${PWD}\pgdata:/var/lib/postgresql/data postgres:16
PS> docker exec fix5-pg-ne psql -U postgres -c "SHOW data_directory;" -c "SELECT version();"
data_directory
---------------------------------
/var/lib/postgresql/data/pgdata
(1 row)
version
----------------------------------------------------------------------------------------------------------------------
PostgreSQL 16.14 (Debian 16.14-1.pgdg13+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
(1 row)
One level down, initdb gets an empty folder of its own and the .gitkeep stays
where it was; Get-ChildItem -Force pgdata afterwards listed both. The other
route is a named volume, -v fix5-pg-ne:/var/lib/postgresql/data, which started
straight away and is also much faster on Windows: in a separate run on this PC with
postgres:18, pgbench loaded a million rows in 0.78 s on a named volume and 29.38 s on a bind mount. For a volume
made by 18, run postgres:18 with it mounted at /var/lib/postgresql.
How it was reproduced
Docker Desktop 4.95.0 (engine 29.8.2) on Windows 11, Windows PowerShell 5.1, images
postgres:16 (16.14) and postgres:18 (18.4). A new folder with an
empty pgdata\.gitkeep, made with New-Item, was bind-mounted at
/var/lib/postgresql/data; the container printed the block above and stopped
with Exited (1). The second case used a named volume first initialised by
postgres:18. Names beginning fix5- are this test's own containers
and volumes, and the port is published on 127.0.0.1 only.
Frequently asked
- How do I fix 'initdb: directory exists but is not empty' in Docker?
- Set PGDATA to a subdirectory of the mount, such as /var/lib/postgresql/data/pgdata, or mount an empty named volume at /var/lib/postgresql/data. Both started postgres:16 in this test. Check what is in the folder before deleting anything; it may be another version's database.
- Why is my postgres data folder not empty when I just created it?
- Look for hidden files. A .gitkeep committed to keep the folder in Git is enough, and initdb's detail line says so when the file starts with a dot. Get-ChildItem -Force on Windows or ls -la in a shell shows it.
- Can postgres:16 open a volume created by postgres:18?
- No. Mounted at /var/lib/postgresql/data it stops with this error; mounted at /var/lib/postgresql it starts a new, empty 16 cluster beside the 18 one; pointed at the 18 files with PGDATA it stops on a setting in postgresql.conf that 16 does not recognise. Run postgres:18 on that volume.
More decoded errors in the Fixes category. Bind mounts and named volumes on Windows, side by side, are covered in Files In and Out: Bind Mounts and Volumes on Windows.