Skip to content

PostgreSQL 18: Init scripts in /docker-entrypoint-initdb.d/ never execute due to automatic version subdirectory creation #1373

@JoMiPeCa

Description

@JoMiPeCa

Bug Report: PostgreSQL 18 - Initialization scripts in /docker-entrypoint-initdb.d/ never execute

Description

Scripts placed in /docker-entrypoint-initdb.d/ are never executed when using PostgreSQL 18 Docker image because the data directory is never considered "empty" due to automatic creation of version subdirectory.

Environment

  • PostgreSQL Version: 18 (postgres:18)
  • Docker Version: 24.x+
  • Docker Compose Version: 2.40.0
  • Host OS: Linux (Ubuntu/Debian based)

Steps to Reproduce

  1. Create a docker-compose.yml:
services:
  postgres:
    image: postgres:18
    container_name: test-postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: test123
      POSTGRES_DB: postgres
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./init:/docker-entrypoint-initdb.d
    ports:
      - "5432:5432"

volumes:
  postgres-data:
  1. Create init script at ./init/01-init.sql:
CREATE DATABASE testdb;
  1. Start container with empty volume:
docker compose down -v
docker compose up -d
  1. Check if database was created:
docker exec test-postgres psql -U postgres -c '\l'

Expected Behavior

The testdb database should be created as per the init script in /docker-entrypoint-initdb.d/.

Actual Behavior

The init script is never executed. Only default databases (postgres, template0, template1) exist.

The script exists in the container:

$ docker exec test-postgres ls -la /docker-entrypoint-initdb.d/
-rw-r--r--. 1 1050  993 1563 Oct 16 07:27 01-init-databases.sql

But it was never executed.

Root Cause Analysis

Upon investigation, we found that:

  1. PostgreSQL 18 creates a version subdirectory automatically on startup:
$ docker exec test-postgres ls -la /var/lib/postgresql/data/
total 12
drwxrwxrwt. 3 postgres postgres 4096 Oct 16 07:36 .
drwxr-xr-x. 1 root     root     4096 Sep 30 00:06 ..
drwxr-xr-x. 3 root     root     4096 Oct 16 07:36 18  # <-- Created automatically
lrwxrwxrwx. 1 root     root        1 Sep 30 00:06 data -> .
  1. The postgres:18 image has a symlink in /var/lib/postgresql/:
$ docker run --rm postgres:18 ls -la /var/lib/postgresql/
lrwxrwxrwx. 1 root     root        1 Sep 30 00:06 data -> .  # <-- Symlink in base image
  1. The docker-entrypoint.sh only runs init scripts if the data directory is empty (per documentation)
  2. Because the 18/ subdirectory is created during initialization, the directory is no longer "empty"
  3. Therefore, initialization scripts are skipped

Version Comparison

This issue does NOT occur with PostgreSQL 17 or earlier versions:

PostgreSQL 17:

$ docker run --rm postgres:17 ls -la /var/lib/postgresql/data/
# Directory starts empty, init scripts execute correctly ✅

PostgreSQL 18:

$ docker run --rm postgres:18 ls -la /var/lib/postgresql/data/
# Directory contains '18/' subdirectory immediately
# Init scripts never execute ❌

Impact

  • Severity: Critical
  • All initialization scripts are silently ignored
  • Users cannot pre-seed databases on first container startup
  • Breaking change from PostgreSQL 17 behavior
  • No error messages or warnings are shown to indicate scripts were skipped
  • Breaks existing docker-compose configurations that worked perfectly with PostgreSQL ≤17

Workaround

Manually execute init scripts after container starts:

docker exec -i test-postgres psql -U postgres < init/01-init.sql

Or add a custom entrypoint wrapper to run scripts manually.

Proposed Fix

Option 1: Modify docker-entrypoint.sh to check if the data directory contains only the version subdirectory (e.g., 18/) and the data symlink, and treat it as "empty" for initialization purposes.

Option 2: Delay the creation of the version subdirectory until after init scripts have been executed.

Option 3: Add a flag or environment variable to force execution of init scripts even when directory is not empty.

Additional Context

  • The symlink data -> . has existed in previous PostgreSQL versions without causing this issue
  • The automatic creation of the 18/ subdirectory appears to be new behavior in PostgreSQL 18
  • This breaks existing production workflows and CI/CD pipelines
  • Spent 3+ hours debugging before identifying root cause
  • Multiple users are likely affected but may not realize their init scripts are being silently ignored

Related Issues

Environment Details

PostgreSQL: postgres:18 (latest as of October 2025)
Docker: 24.0+
Docker Compose: 2.40.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions