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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions