How to Restore a Database Dump Into a Docker Container (MySQL, MariaDB & PostgreSQL)
Restoring a SQL dump into a containerized database is one of the most common operations you will perform when running databases in Docker, whether you are migrating a production dataset, seeding a staging environment, or rebuilding from a backup. The process looks simple on the surface, but the right method depends entirely on whether your container is brand new or already holding data. Choose the wrong approach and your import will silently do nothing.
This guide walks through every reliable method to restore a database dump into a Docker database container for MySQL, MariaDB, and PostgreSQL, complete with copy-paste commands and the gotchas that trip up most engineers.
Key Takeaways
• Use `docker exec -i` to pipe a dump straight into a running container — this is the most reliable method for existing databases.
• The `/docker-entrypoint-initdb.d/` auto-import only runs on first initialization when the data volume is empty. It is skipped entirely if the container already has data.
• For large dumps, `docker cp` the file into the container first, then import — this avoids stream and timeout issues.
• Always confirm the database is ready to accept connections before importing; the container being “up” is not the same as the database being ready.
• Character set and collation mismatches are a leading cause of corrupted imports — match them to the source dump.
Why is restoring into a container different from a normal restore?
When a database runs on a bare host, you simply point your client at `localhost` and pipe in the dump. With Docker, the database process runs inside an isolated container with its own filesystem, network namespace, and a data directory that is usually backed by a named volume or bind mount.
That isolation introduces three realities you must account for:
- The dump file lives outside the container. You need to either stream it in over `stdin` or copy it across the container boundary.
- The official images have an initialization phase. Images like `mysql`, `mariadb`, and `postgres` run special entrypoint scripts the very first time they start — but only once.
- “Container started” is not “database ready.” The container can report healthy while the database engine is still bootstrapping, especially on first run.
Understanding these three points is what separates a clean restore from a frustrating silent failure.
What are the methods to restore a dump into a Docker DB container?
There are four practical approaches. The table below summarizes when to reach for each.
| Method | Best for | Runs on existing data? | How it works |
|---|---|---|---|
| `docker exec -i` (pipe) | Running container with or without data | Yes | Streams the dump over `stdin` into the DB client inside the container |
| `docker cp` + exec | Large dumps, running container | Yes | Copies the file into the container, then imports it locally |
| Init scripts (`/docker-entrypoint-initdb.d/`) | Fresh container, first boot only | No | Auto-runs `.sql`/`.sh` files when the data volume is empty |
| Docker Compose + init scripts | Reproducible fresh environments | No | Mounts init scripts declaratively for first-time setup |
Let’s look at each in detail.
How do I pipe a dump in with docker exec?
This is the go-to method for any container that is already running, and it works whether the database is empty or already populated. You stream the dump file from your host directly into the database client running inside the container.
For MySQL or MariaDB:
“`bash
docker exec -i my-mysql mysql -u root -pSECRET mydatabase < dump.sql
cat dump.sql | docker exec -i my-mysql mysql -u root -pSECRET mydatabase
gunzip < dump.sql.gz | docker exec -i my-mysql mysql -u root -pSECRET mydatabase ```
The `-i` flag is critical — it keeps `stdin` open so the redirected file actually reaches the client. Without it, the dump never arrives and the command appears to succeed while importing nothing.
For PostgreSQL, the equivalent uses `psql` for plain-SQL dumps and `pg_restore` for custom-format archives:
“`bash
docker exec -i my-postgres psql -U postgres -d mydatabase < dump.sql
docker exec -i my-postgres pg_restore -U postgres -d mydatabase < dump.dump ```
For Postgres, you typically pass the password through the `PGPASSWORD` environment variable rather than on the command line:
“`bash docker exec -i -e PGPASSWORD=SECRET my-postgres \ psql -U postgres -d mydatabase < dump.sql ```
How do I copy the dump in first with docker cp?
Piping over `stdin` is elegant, but for very large dumps it can be more robust to copy the file into the container and import it from inside. This decouples the network/stream from the import and makes long-running restores easier to monitor.
“`bash
docker cp dump.sql my-mysql:/tmp/dump.sql
docker exec my-mysql sh -c \ ‘mysql -u root -pSECRET mydatabase < /tmp/dump.sql'
docker exec my-mysql rm /tmp/dump.sql “`
For PostgreSQL:
“`bash docker cp dump.sql my-postgres:/tmp/dump.sql docker exec my-postgres sh -c \ ‘psql -U postgres -d mydatabase -f /tmp/dump.sql’ “`
Note the use of `sh -c` so the redirection (`<`) or `-f` flag is evaluated inside the container rather than on your host shell.
How do init scripts in /docker-entrypoint-initdb.d/ work?
The official `mysql`, `mariadb`, and `postgres` images support a powerful convenience: any `.sql`, `.sql.gz`, or `.sh` file placed in `/docker-entrypoint-initdb.d/` is executed automatically when the container initializes its database.
“`bash docker run -d \ –name fresh-db \ -e MYSQL_ROOT_PASSWORD=SECRET \ -e MYSQL_DATABASE=mydatabase \ -v “$(pwd)/dump.sql:/docker-entrypoint-initdb.d/dump.sql” \ mysql:8 “`
On first boot, the image creates `mydatabase`, then runs `dump.sql` against it. This is the cleanest way to ship a pre-seeded, reproducible database image.
The gotcha that catches almost everyone: the `/docker-entrypoint-initdb.d/` scripts run only when the data directory is empty — that is, on the very first initialization of the volume. The entrypoint checks whether the data directory already contains a database; if it does, it skips the init scripts entirely and just starts the server.
This means:
- If you mount a dump into an init folder on a container whose volume already has data, your dump is silently ignored. Nothing is imported, no error is shown.
- Re-running `docker compose up` on an existing project will not re-run your seed scripts.
- To force a re-init, you must delete the volume (`docker volume rm …` or `docker compose down -v`) so the data directory starts empty again — a destructive operation you must do deliberately.
For an existing container that already holds data, init scripts are the wrong tool. Use the `docker exec` pipe method instead.
How do I set this up with Docker Compose?
Compose makes the init-script pattern declarative and repeatable. This is ideal for spinning up consistent development and CI environments.
“`yaml services: db: image: postgres:16 environment: POSTGRES_PASSWORD: SECRET POSTGRES_DB: mydatabase volumes:
- db_data:/var/lib/postgresql/data
- ./dump.sql:/docker-entrypoint-initdb.d/01-restore.sql:ro
ports:
- “5432:5432”
volumes: db_data: “`
Prefixing files with numbers (`01-`, `02-`) controls execution order. Remember the same rule applies: this restore runs only if `db_data` is empty. After the first `docker compose up`, the named volume persists and the script will not run again. To reset, run `docker compose down -v`.
What are the most common gotchas when restoring?
Beyond the empty-volume rule, watch for these:
- Database not ready yet. On first boot the engine may still be initializing when your import fires. Wait for readiness before importing into a freshly started container:
“`bash
until docker exec my-mysql mysqladmin ping -h localhost –silent; do sleep 2 done
until docker exec my-postgres pg_isready -U postgres; do sleep 2 done “`
- Character set and collation mismatch. If the source dump uses `utf8mb4` but the target database defaults to a different charset, you can corrupt multi-byte text. Create the target database with a matching charset, e.g. `CREATE DATABASE mydatabase CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`.
- Large dumps and timeouts. Multi-gigabyte restores can exceed default packet sizes and connection timeouts. For MySQL, raise `max_allowed_packet` (e.g. `–max-allowed-packet=512M`) and prefer the `docker cp` method so the import is not bound to a host-side stream.
- Wrong target database. Make sure the database named on the command line actually exists, or that your dump includes `CREATE DATABASE`/`USE` statements. Piping into a non-existent database fails fast.
- Privileges. Restores that recreate users, grants, or extensions need a sufficiently privileged role (`root` for MySQL, a superuser for Postgres).
Where can you reliably run Dockerized databases?
Restoring dumps is straightforward when your host gives you full control and enough headroom. The bottleneck is rarely the command — it is root access, raw I/O throughput, and resources to absorb a large import without throttling.
DarazHost VPS and dedicated servers are built for exactly this kind of containerized workload. You get full root access and Docker support to run MySQL, MariaDB, PostgreSQL, or any containerized application stack the way you want it. Restores and imports move fast on SSD-backed storage, and generous CPU and RAM allocations mean multi-gigabyte dumps complete without choking your other services.
Because Dockerized databases are only as reliable as the infrastructure underneath them, every plan includes dependable storage, predictable performance, and 24/7 expert support for your containerized workloads — so when a restore or migration needs to happen at 3 a.m., someone is there to help. Whether you are hosting a single Dockerized app or an entire eCommerce platform behind containers, DarazHost gives you the foundation to run it confidently.
Frequently Asked Questions
Q: Why is my dump in /docker-entrypoint-initdb.d/ being ignored? A: Because the data volume already contains an initialized database. Init scripts run only on first initialization when the data directory is empty. Either delete the volume (`docker compose down -v`) to start fresh, or import manually with `docker exec -i`.
Q: Do I need the -i flag with docker exec? A: Yes, when piping a dump over `stdin`. The `-i` (interactive) flag keeps `stdin` open so the redirected file reaches the database client. Without it the command runs but imports nothing.
Q: How do I restore a compressed (.gz) dump? A: Decompress on the fly and pipe the output: `gunzip < dump.sql.gz | docker exec -i my-mysql mysql -u root -pSECRET mydatabase`. The same pattern works for PostgreSQL with `psql`.
Q: Should I use psql or pg_restore for PostgreSQL? A: Use `psql` for plain-text SQL dumps (created with `pg_dump` default output) and `pg_restore` for custom or directory-format archives (created with `pg_dump -Fc` or `-Fd`). They are not interchangeable.
Q: How do I restore a very large dump without timing out? A: Copy the file into the container with `docker cp` first, then run the import from inside the container with `docker exec`. For MySQL, also raise `max_allowed_packet`. This avoids host-side stream interruptions on long-running imports.