Skip to main content

How to Stop, Start and Remove Docker Compose Containers Without Losing Data

· 7 min read
Jagdish Kumawat
Founder @ Dewiride

Docker Compose has four everyday commands for stopping and starting your containers, and they differ in what they remove. Three of them keep your database's data; one deletes it for good. This guide runs each one against a real MySQL database and proves the data is still there afterwards, so you know which command to reach for.

Containers Come and Go, Volumes Stay​

A container is the running program: here, MySQL. It's meant to be disposable: Compose removes and recreates it whenever it needs to. A named volume is storage that Docker keeps outside the container, and that's where the database files live.

The project in this guide is the MySQL 9.7 database from How to Run MySQL in Docker with Docker Compose. Its compose.yaml maps MySQL's data folder to a named volume:

compose.yaml (the parts that matter here)
services:
mysql:
volumes:
- mysql-data:/var/lib/mysql

volumes:
mysql-data:

As long as that volume exists, you can remove the container as often as you like.

CommandContainerNetworkNamed volume (your data)
docker compose stopStopped, keptKeptKept
docker compose startStarted againKeptKept
docker compose downRemovedRemovedKept
docker compose down -vRemovedRemovedDeleted

Prerequisites​

  • A Compose project with a named volume, such as the MySQL one above, running and healthy.
  • A terminal in the project folder, the one with compose.yaml. Every command here works unchanged in bash, zsh and PowerShell.

Step 1: Stop the Container​

Terminal
docker compose stop
Output
Container dewiride-inventory-mysql-1 Stopping
Container dewiride-inventory-mysql-1 Stopped

MySQL shuts down cleanly and its memory is freed. The container still exists, which ps shows with --all (plain ps lists only running containers):

Terminal
docker compose ps --all
Output
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
dewiride-inventory-mysql-1 mysql:9.7 "docker-entrypoint.s…" mysql About a minute ago Exited (0) Less than a second ago

Exited (0) means it stopped normally, with exit code 0.

Step 2: Start It Again​

Terminal
docker compose start --wait
Output
Container dewiride-inventory-mysql-1 Starting
Container dewiride-inventory-mysql-1 Started
Container dewiride-inventory-mysql-1 Waiting
Container dewiride-inventory-mysql-1 Healthy

--wait waits for the health check, so when the command returns, MySQL is ready. That's the same container, with the same data.

A terminal where docker compose stop stops the MySQL container, docker compose ps --all shows it as Exited (0), and docker compose start --wait brings it back to Healthy.

Step 3: Remove the Container but Keep the Data​

Terminal
docker compose down
Output
Container dewiride-inventory-mysql-1 Stopping
Container dewiride-inventory-mysql-1 Stopped
Container dewiride-inventory-mysql-1 Removing
Container dewiride-inventory-mysql-1 Removed
Network dewiride-inventory_default Removing
Network dewiride-inventory_default Removed

down "stops and removes containers, networks", in the words of its own --help. The output never mentions the volume, because down leaves it alone. Check that it's still there:

Terminal
docker volume ls --filter name=dewiride-inventory
Output
DRIVER VOLUME NAME
local dewiride-inventory_mysql-data

Step 4: Bring It Back​

Terminal
docker compose up -d --wait
Output
Network dewiride-inventory_default Creating
Network dewiride-inventory_default Creating
Network dewiride-inventory_default Created
Network dewiride-inventory_default Created
Container dewiride-inventory-mysql-1 Creating
Container dewiride-inventory-mysql-1 Created
Container dewiride-inventory-mysql-1 Starting
Container dewiride-inventory-mysql-1 Started
Container dewiride-inventory-mysql-1 Waiting
Container dewiride-inventory-mysql-1 Healthy

A terminal where docker compose down removes the MySQL container and network, docker volume ls still lists dewiride-inventory_mysql-data, and docker compose up -d --wait creates a new container that turns Healthy.

A brand-new container, but the old data. The MySQL log proves it: on the very first start, MySQL printed Initializing database files and created the database. This time it skipped straight to being ready:

Terminal
docker compose logs mysql
Output (shortened)
mysql-1 | 2026-11-02 14:50:47+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 9.7.4-1.el9 started.
…
mysql-1 | 2026-11-02T14:50:48.452003Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '9.7.4' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.

And the inventory database and its account are still there:

Output (mysql client, SHOW DATABASES)
+--------------------+
| Database |
+--------------------+
| information_schema |
| inventory |
| performance_schema |
+--------------------+
3 rows in set (0.000 sec)

The One Command That Deletes Your Data​

Terminal
docker compose down -v

-v (or --volumes), in Docker's own words: "Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers." For a database, that means every database, table and row. The next docker compose up starts from an empty database.

There is no undo for down -v

Use down -v only when you really want an empty database, for example to start a development set-up from scratch. If the data matters, take a backup first.

Common Mistakes​

MistakeWhat happensDo this instead
Using down -v to "restart cleanly"The database is deletedUse stop/start, or down without -v
Mapping the data folder without a named volumeRemoving the container removes the data with itUse a named volume for /var/lib/mysql
Running commands from another folderCompose can't find the projectcd into the folder with compose.yaml
Expecting .env changes to apply to an existing databaseDocker recreates the container, but MySQL keeps the old accountsSee Fixed: ERROR 1045 (28000): Access denied for user

Troubleshooting​

SymptomLikely causeFix
docker compose ps shows nothing after stopPlain ps lists only running containersUse docker compose ps --all
The volume is missing from docker volume lsIt was removed with down -v or docker volume rmRecreate the data from a backup, or start fresh
MySQL didn't come back after restarting your computerThis compose.yaml sets no restart policyRun docker compose start --wait

FAQ​

Should I use stop or down at the end of the day?​

stop. It frees memory and keeps everything ready for start. down is for when you want to remove the containers too. For example, after changing compose.yaml in a way that needs fresh ones, although up usually recreates them for you.

Does down delete the MySQL image?​

No. Images are only removed when you add --rmi. The next up reuses the downloaded image.

How do I delete everything when I'm truly done?​

docker compose down -v removes the containers, the network and the volume. docker image rm mysql:9.7 then frees the disk space the image uses. Do both only when you no longer need the data.

Conclusion​

docker compose stop, start, down and up all keep a named volume, so your database survives them. The proof is a recreated container that starts with the old data and skips initialization. Only docker compose down -v deletes the volume, so save it for when you really want to start over.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.