Databases are the most common thing developers run in Docker, and the easiest to lose data with. This lesson covers the three most popular official images, the variables that configure them, where each stores its data, how to seed a fresh database with initialisation scripts, and how to back up and restore. After it you will run any of them safely for development and know what changes for production.
| | PostgreSQL | MySQL | MongoDB |
|---|---|---|---|
| Image | postgres:17 | mysql:8.4 | mongo:8 |
| Required variable | POSTGRES_PASSWORD | MYSQL_ROOT_PASSWORD | MONGO_INITDB_ROOT_USERNAME + _PASSWORD |
| Create user and db | POSTGRES_USER, POSTGRES_DB | MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE | MONGO_INITDB_DATABASE |
| Data directory | /var/lib/postgresql/data | /var/lib/mysql | /data/db |
| Init scripts | /docker-entrypoint-initdb.d/ (.sql, .sql.gz, .sh) | same path (.sql, .sh) | same path (.js, .sh) |
| Port | 5432 | 3306 | 27017 |
| Readiness check | pg_isready -U app -d shop | mysqladmin ping -h localhost | mongosh --eval "db.adminCommand('ping')" |
| CLI | psql | mysql | mongosh |
All three accept the _FILE variant of their password variables, so Compose secrets work without change. Pin a major version tag: postgres:17 receives patch updates while postgres:latest may jump a major version and refuse to open the old data directory.
Without a volume, the database writes into the container's writable layer and everything disappears with docker rm. Always mount a named volume on the data directory:
docker run -d --name pg \
-e POSTGRES_PASSWORD=devpass -e POSTGRES_USER=app -e POSTGRES_DB=shop \
-v pg-data:/var/lib/postgresql/data \
-p 127.0.0.1:5432:5432 \
postgres:17Prefer named volumes over bind mounts for database files: they avoid permission problems and are much faster on Docker Desktop. docker compose down keeps the volume; docker compose down -v deletes it.
Each image runs the files in /docker-entrypoint-initdb.d/ in alphabetical order, only when the data directory is empty. This is the place for schema creation and seed data in development:
-- db/init/01-schema.sql
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
price_cents INTEGER NOT NULL
);
INSERT INTO products (name, price_cents) VALUES ('Notebook', 499), ('Pen', 149);Mount the directory read-only, as in the sample code. If the scripts do not seem to run, the volume already holds an initialised database; remove it with docker compose down -v and start again. For MongoDB, .js files run through mongosh against MONGO_INITDB_DATABASE.
Inside the Compose network, applications use the service name as the host: postgres://app:app@postgres:5432/shop, mysql://app:app@mysql:3306/shop, mongodb://root:pass@mongo:27017/shop?authSource=admin. From the host, use the published port on 127.0.0.1. The quickest client is the one already in the image:
docker compose exec postgres psql -U app -d shop -c '\dt'
docker compose exec mysql mysql -u root -p"$MYSQL_ROOT_PASSWORD" shop
docker compose exec mongo mongosh -u root -p pass --authenticationDatabase adminDump through docker exec and redirect on the host; restore by piping into the client with -i (and no -t, which would corrupt the stream):
# PostgreSQL
docker compose exec -T postgres pg_dump -U app -Fc shop > shop.dump
docker compose exec -T postgres pg_restore -U app -d shop --clean < shop.dump
# MySQL
docker compose exec -T mysql mysqldump -u root -p"$PW" shop > shop.sql
docker compose exec -T mysql mysql -u root -p"$PW" shop < shop.sql
# MongoDB (archive to stdout)
docker compose exec -T mongo mongodump --archive -u root -p pass > shop.archive
docker compose exec -T mongo mongorestore --archive -u root -p pass < shop.archiveLogical dumps are the portable option; copying the volume directory with tar is only safe while the database is stopped.
POSTGRES_PASSWORD_FILE (or the equivalent) with a secret instead of inline passwords, and add restart: unless-stopped.pg_upgrade); the new image will not start on the old data directory.When do the scripts in `/docker-entrypoint-initdb.d/` run?
_FILE variants, and stores data at a documented path that must be a named volume.latest jump can make the data directory unreadable.down -v to re-run them.127.0.0.1 on a published port only in development.pg_dump, mysqldump or mongodump through exec -T, and never publish the database port in production.Next lesson: Dockerizing a Node.js Application — build a production-ready image for an Express or Fastify service.