docker-compose
PSQL
database initialization
containerization
PostgreSQL setup

How to initialize a PSQL database in docker-compose file?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The easiest way to initialize PostgreSQL in Docker Compose is to use the official postgres image and mount SQL or shell files into /docker-entrypoint-initdb.d. On first startup, the container creates the database directory, creates the configured database and user, and runs those initialization files in order.

The detail that trips people up is "first startup." If your data volume already contains a database cluster, the init scripts will not run again, so repeated edits to init.sql appear to do nothing.

Basic Compose Setup

A minimal docker-compose.yml can define the database, credentials, port mapping, and a persistent volume:

yaml
1services:
2  db:
3    image: postgres:16
4    container_name: app-db
5    environment:
6      POSTGRES_USER: app_user
7      POSTGRES_PASSWORD: secret123
8      POSTGRES_DB: app_db
9    ports:
10      - "5432:5432"
11    volumes:
12      - db_data:/var/lib/postgresql/data
13      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
14
15volumes:
16  db_data:

When the container starts with an empty data directory, PostgreSQL creates app_db and then executes init.sql.

Add Seed SQL

Your initialization file can create tables, indexes, and seed data:

sql
1CREATE TABLE users (
2    id SERIAL PRIMARY KEY,
3    email TEXT NOT NULL UNIQUE,
4    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
5);
6
7INSERT INTO users (email)
8VALUES ('[email protected]');

Bring the service up with:

bash
docker compose up -d
docker compose logs -f db

The logs are the quickest way to confirm that the entrypoint detected an empty database and executed the script.

Initialize with Multiple Files

You are not limited to one SQL file. The image executes files in lexical order, so a directory mount is convenient for larger setups:

yaml
1services:
2  db:
3    image: postgres:16
4    environment:
5      POSTGRES_USER: app_user
6      POSTGRES_PASSWORD: secret123
7      POSTGRES_DB: app_db
8    volumes:
9      - db_data:/var/lib/postgresql/data
10      - ./docker/postgres-init:/docker-entrypoint-initdb.d:ro

An example directory could look like this:

text
1docker/postgres-init/
2  01-schema.sql
3  02-seed.sql
4  03-extensions.sh

Shell scripts are also supported, which is useful if you need conditional logic or additional psql commands.

Re-Running Initialization During Development

Because init scripts only run against an empty data directory, local development often requires resetting the volume:

bash
docker compose down -v
docker compose up -d

That removes the named volume and forces PostgreSQL to initialize from scratch. Do not use this in environments where you care about preserving data.

If you need repeatable schema changes after the database already exists, use a migration tool such as Flyway, Liquibase, Alembic, or your framework's built-in migration system. Init scripts are for bootstrap, not ongoing schema evolution.

Verify the Database

You can connect from the host or from inside the container:

bash
psql postgresql://app_user:secret123@localhost:5432/app_db

Or:

bash
docker exec -it app-db psql -U app_user -d app_db

Then confirm the seeded table exists:

sql
\dt
SELECT * FROM users;

Common Pitfalls

  • Editing init.sql and expecting changes to apply to an existing volume. The entrypoint skips init scripts once data already exists.
  • Forgetting to mount scripts into /docker-entrypoint-initdb.d, which means the container starts normally but nothing is seeded.
  • Using production credentials directly in the Compose file. Prefer environment files or secrets for real deployments.
  • Relying on init scripts for schema upgrades. Use migrations once the database is no longer disposable.
  • Mounting a Windows-formatted script with line-ending issues can break shell-based init scripts. SQL files are usually less sensitive, but shell files should use Unix line endings.

Summary

  • Use the official postgres image with POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB.
  • Mount SQL or shell files into /docker-entrypoint-initdb.d.
  • Initialization runs only when the database directory is empty.
  • Reset the volume during development if you need bootstrap scripts to run again.
  • Use a migration tool for changes after the first initialization.

Course illustration
Course illustration

All Rights Reserved.