Running Databases in Docker
Installing a database the traditional way (with apt) puts it permanently on your machine, ties you to one version, and can be a pain to remove. Docker (a tool that runs software inside lightweight, isolated boxes called containers) lets you start a fresh Postgres, MySQL, MongoDB, or Redis in seconds, run several versions side by side, and throw them away when you are done. This page shows you the exact commands to run each database in Docker, how to keep your data safe with volumes, and how to wire it all together with Docker Compose.
Why run a database in a container
A container is a running copy of a packaged image (a read-only template that bundles the database software and everything it needs). Because the container is isolated, the database does not touch your host operating system. You get a clean, repeatable setup that behaves the same on every machine.
When to use this: local development, automated tests, trying a new database version, or running throwaway environments. It is the fastest way to get a database for a project without polluting your laptop.
When NOT to use this: a serious production database that holds real customer data. Containers add a layer of complexity around storage, backups, and networking that you must get exactly right, and a single bad docker rm can wipe everything. For production, prefer a managed service (like Amazon RDS) or a carefully configured dedicated server. See the caution below.
First, install Docker on Ubuntu 22.04/24.04 LTS if you have not already:
sudo apt update
sudo apt install -y docker.io
sudo systemctl enable --now docker
sudo usermod -aG docker $USER # log out and back in for this to take effect
Check it works:
docker --version
Output:
Docker version 27.5.1, build 27.5.1-0ubuntu3~24.04.1
The big gotcha: data lives in a volume
This is the single most important idea on this page. By default, anything a container writes is stored inside the container. When you run docker rm to delete that container, all the data goes with it. People lose their database constantly this way.
The fix is a named volume (a storage area that Docker manages on your host and keeps alive independently of any container). You attach the volume to the folder where the database keeps its files. The container can come and go; the volume — and your data — stays.
Warning: If you run a database container without a volume, your data is deleted the moment the container is removed. Always map a named volume to the database’s data directory. There is no undo.
Running PostgreSQL
PostgreSQL stores its data in /var/lib/postgresql/data inside the container. We map a named volume there so the data survives.
docker run -d \
--name pg \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_USER=appuser \
-e POSTGRES_DB=appdb \
-p 5432:5432 \
-v pgdata:/var/lib/postgresql/data \
postgres:17
What each flag means:
| Flag | Meaning |
|---|---|
-d | Run detached (in the background) |
--name pg | Give the container a friendly name |
-e KEY=VALUE | Set an environment variable the image reads on startup |
-p 5432:5432 | Map host port : container port so apps on your laptop can connect |
-v pgdata:/var/lib/... | Attach the named volume pgdata for persistence |
Connect to it from inside the container to confirm:
docker exec -it pg psql -U appuser -d appdb -c "SELECT version();"
Output:
version
----------------------------------------------------------------------------
PostgreSQL 17.4 (Debian 17.4-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled...
(1 row)
Running MySQL
docker run -d \
--name mysql \
-e MYSQL_ROOT_PASSWORD=rootsecret \
-e MYSQL_DATABASE=appdb \
-e MYSQL_USER=appuser \
-e MYSQL_PASSWORD=secret \
-p 3306:3306 \
-v mysqldata:/var/lib/mysql \
mysql:8.4
MySQL keeps its data in /var/lib/mysql. Test the login:
docker exec -it mysql mysql -u appuser -psecret -e "SELECT 1;"
Output:
+---+
| 1 |
+---+
| 1 |
+---+
Running MongoDB
docker run -d \
--name mongo \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=secret \
-p 27017:27017 \
-v mongodata:/data/db \
mongo:8
MongoDB stores data in /data/db. Verify with its shell:
docker exec -it mongo mongosh -u admin -p secret --eval "db.runCommand({ ping: 1 })"
Output:
{ ok: 1 }
Running Redis
Redis is an in-memory store, but it can save snapshots to disk. Turn on persistence with --appendonly yes and give it a volume.
docker run -d \
--name redis \
-p 6379:6379 \
-v redisdata:/data \
redis:7 redis-server --appendonly yes --requirepass secret
Output (after docker exec -it redis redis-cli -a secret PING):
PONG
Doing it all with Docker Compose
Typing long docker run commands gets old fast. Docker Compose lets you describe your containers in one docker-compose.yml file and start everything with a single command. Create the file:
services:
postgres:
image: postgres:17
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7
command: redis-server --appendonly yes --requirepass secret
ports:
- "6379:6379"
volumes:
- redisdata:/data
restart: unless-stopped
volumes:
pgdata:
redisdata:
Start and stop the whole stack:
docker compose up -d # start everything in the background
docker compose ps # see what is running
docker compose down # stop and remove containers (volumes are KEPT)
Output of docker compose ps:
NAME IMAGE STATUS PORTS
project-postgres-1 postgres:17 Up 8 seconds 0.0.0.0:5432->5432/tcp
project-redis-1 redis:7 Up 8 seconds 0.0.0.0:6379->6379/tcp
Note that docker compose down keeps your named volumes. To delete the data too, run docker compose down -v — only do this when you truly want a clean slate.
Inspecting and removing volumes
docker volume ls # list all volumes
docker volume inspect pgdata
docker volume rm pgdata # permanently delete (container must be gone first)
Tip: On Ubuntu, named volumes live under
/var/lib/docker/volumes/. Do not edit those files by hand — let the database manage them. To move data between machines, use the database’s own backup tools (likepg_dumpormongodump), not a raw file copy.
Best practices
- Always attach a named volume to the data directory so a
docker rmnever destroys your data. - Pin a specific image tag (
postgres:17, notpostgres:latest) so rebuilds are reproducible and a surprise major upgrade does not break you. - Never hardcode real passwords in committed Compose files — use a
.envfile or Docker secrets, and add.envto.gitignore. - Only publish the port (
-p) when you actually need host access; on a shared server bind to localhost with-p 127.0.0.1:5432:5432so the database is not exposed to the internet. - Use
restart: unless-stoppedso dev databases come back after a reboot. - Take regular backups with the database’s native dump tools, and test that you can restore them.
- Treat container databases as disposable for dev; for production, prefer a managed database or a hardened dedicated install.