docker-compose
postgresql
database dump
data import
devops

Docker-Compose Postgresql import dump

Master System Design with Codemia

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

Introduction

Importing a PostgreSQL dump with Docker Compose is straightforward once you separate two very different cases: initializing a brand-new database and loading data into an already initialized one. Most confusion comes from expecting the image startup hooks to rerun after the database volume already contains data.

Know Which Kind of Dump You Have

PostgreSQL dumps are not all restored the same way. The common formats are:

  • plain SQL, usually created with pg_dump > dump.sql
  • custom or directory format, usually created with pg_dump -Fc

Plain SQL is restored with psql. Custom format is restored with pg_restore. If you use the wrong tool, the restore fails even though the container itself is healthy.

Case 1: Initialize a Fresh Database Automatically

The official PostgreSQL image runs files from /docker-entrypoint-initdb.d/ only when the database directory is empty. That makes it ideal for first-time setup.

Example compose.yaml:

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

If ./init.sql contains valid SQL, the database is created and the script runs on the first startup of that volume.

Start it with:

bash
docker compose up -d

This approach is excellent for local development seeds and reproducible demos. It is not the right tool for repeated imports into an existing database.

Case 2: Restore into an Existing Container

If the database has already been initialized, startup scripts in /docker-entrypoint-initdb.d/ will not run again. In that case, execute the restore manually.

For a plain SQL dump:

bash
docker compose exec -T db psql \
  -U appuser \
  -d appdb < dump.sql

For a custom-format dump:

bash
1docker compose exec -T db pg_restore \
2  -U appuser \
3  -d appdb \
4  --clean \
5  --if-exists < dump.dump

The -T flag disables pseudo-TTY allocation, which avoids input handling issues when piping a file into the command.

When You Need to Re-Run Initialization

Sometimes developers change init.sql, restart Compose, and expect the new script to run. It will not if the volume still exists.

To force a truly fresh initialization in a local environment, remove the existing volume first:

bash
docker compose down -v
docker compose up -d

That deletes the persisted database data. It is appropriate for disposable local environments, not for databases that contain data you care about.

Verifying That the Import Worked

Do not assume success because the container is running. Query the database directly after the restore.

bash
docker compose exec db psql -U appuser -d appdb -c '\dt'

You can also run a simple row count:

bash
1docker compose exec db psql \
2  -U appuser \
3  -d appdb \
4  -c 'SELECT COUNT(*) FROM users;'

A healthy container only tells you PostgreSQL started. It does not tell you the restore completed or loaded the expected schema and data.

Loading a Dump File from the Host

If you do not want to pipe the file from the host shell, you can copy it into the container first:

bash
docker cp dump.sql "$(docker compose ps -q db)":/tmp/dump.sql
docker compose exec db psql -U appuser -d appdb -f /tmp/dump.sql

That is useful on systems where shell redirection or quoting gets awkward. It also makes it easier to inspect the file inside the container when debugging encoding issues.

A Good Default Workflow

For most teams, the safest pattern is:

  1. Use /docker-entrypoint-initdb.d/ only for first-time local setup.
  2. Use psql or pg_restore for repeatable imports into running containers.
  3. Keep persistent data in a named volume.
  4. Verify the result with actual SQL queries.

That keeps initialization concerns separate from restore operations and avoids the common mistake of relying on startup hooks for recurring imports.

Common Pitfalls

The most common mistake is mounting a dump into /docker-entrypoint-initdb.d/ and expecting it to rerun on every container restart. It runs only when PostgreSQL initializes an empty data directory.

Another mistake is using psql for a custom-format dump created with pg_dump -Fc. That file requires pg_restore.

Teams also forget that an existing named volume outlives the container. Recreating the container is not the same as recreating the database state.

Finally, developers often skip verification and assume the import worked because no obvious error appeared in the startup logs. Always query the restored database directly.

Summary

  • First decide whether you are initializing a fresh database or restoring into an existing one.
  • Files in /docker-entrypoint-initdb.d/ run only for an empty PostgreSQL data directory.
  • Use psql for plain SQL dumps and pg_restore for custom-format dumps.
  • Removing a volume with docker compose down -v resets the database state completely.
  • Confirm the restore with SQL queries instead of assuming success from container health alone.

Course illustration
Course illustration

All Rights Reserved.