The right way to back up a Docker container is to not back up the container at all: you rebuild it from a Dockerfile. But sometimes you inherit a production box where a container has been running for two years, the Dockerfile is gone, and nobody can reproduce it. When that container is one OOM-kill away from taking the business with it, you need a backup now, not a refactor. The fix is `docker commit` to snapshot the writable layer into an image, then `docker save` to export that image as a portable tarball you can copy off-host. This is the break-glass procedure, and below is exactly how I run it without losing data I cannot get back.
How do I snapshot a running container into an image?
`docker commit` freezes the container's writable layer into a brand-new image. Tag it with a date so future-you knows what it is. `-p` pauses the container while the commit runs (it is on by default, but I pass it explicitly) so the writable layer is captured at a single point in time instead of mid-write, and `-a` / `-c` stamp metadata so the image is not anonymous six months from now. One caveat up front: pausing freezes processes, it does not flush a database's in-memory buffers, and it does not touch volumes at all. More on that below.
# -p pauses the container so the writable layer is captured at one point in time
docker commit -p \
-a "raihan <ops@example.com>" \
-c 'CMD ["php-fpm"]' \
myapp-prod myapp:backup-2026-06-17
# confirm the new image exists
docker image ls myappThat gives you an image, but an image sitting in the local daemon's storage is not a backup. If the disk dies, it dies with the container. You need it off the host.
How do I get the image off the host as a portable file?
`docker save` writes the image, all of its layers, its history, and its metadata into a single tarball you can `scp` or `rsync` anywhere. On the destination machine you reverse it with `docker load`, and the image comes back with the same tag and the same runnable config.
# export the image to a tarball (preserves layers, history, ENV, CMD)
docker save -o myapp-backup-2026-06-17.tar myapp:backup-2026-06-17
# copy it somewhere that is not this server
rsync -avz --progress myapp-backup-2026-06-17.tar backup-host:/srv/backups/
# on the other machine, load it back into the daemon
docker load -i myapp-backup-2026-06-17.tar
# Loaded image: myapp:backup-2026-06-17An image in your local daemon is not a backup. A tarball on a different machine is a backup.
Should I use docker save or docker export?
These look interchangeable and they are not. `docker export` flattens a container's filesystem into a single tar stream with no layers, no image history, and no image config: ENV, CMD, ENTRYPOINT, and exposed ports are all dropped. `docker import` reads it back as a fresh, history-less image that will not start the way the original did unless you re-specify the command (with `import --change` or at `docker run` time). Use export/import when you genuinely want a clean, flattened filesystem snapshot. Use save/load when you want the actual runnable image preserved, which during a rescue is almost always what you want.
# export flattens the FS into one layer; ENV/CMD/history are NOT kept
docker export myapp-prod | gzip > myapp-fs.tar.gz
# import brings it back as a single-layer image with no CMD
zcat myapp-fs.tar.gz | docker import - myapp:flat-2026-06-17
# because CMD was lost, you must re-supply the command when you run it
docker run -d myapp:flat-2026-06-17 php-fpmWhy is my database missing from the committed image?
This is the one that bites everyone. `docker commit` only captures the container's writable layer. It does NOT capture named volumes or bind mounts. Your MySQL data lives in a volume, which means after a flawless commit-and-save your backup image contains the binaries and config but zero rows of actual data. You have to back volumes up separately, by running a throwaway container that mounts both the volume and a host directory and tars one into the other. For a database specifically, the cleaner snapshot is a real dump (`mysqldump` / `pg_dump`) taken while it runs, since a raw file-level tar of a live data directory can still be inconsistent.
# back up a named volume into a tarball on the host
docker run --rm \
-v mydata:/data \
-v "$(pwd)":/backup \
alpine tar czf /backup/mydata.tgz -C /data .
# restore: untar into a FRESH empty volume
docker volume create mydata-restored
docker run --rm \
-v mydata-restored:/data \
-v "$(pwd)":/backup \
alpine tar xzf /backup/mydata.tgz -C /dataWhat is the full rescue checklist?
- Commit the container with `-p` and a date-tagged image name.
- Save the image to a tarball with `docker save -o`.
- Back up every named volume and bind mount separately with the alpine tar trick above, and prefer a real `mysqldump` / `pg_dump` for live databases.
- Copy everything (image tarball plus volume tarballs) off the host with rsync or scp.
- Run `docker load` and a volume restore on a different machine and actually boot it before you trust the backup. An untested backup is a rumour.
Commit and save will get you out of the fire, but treat them as exactly that: an escape hatch for a container that was never reproducible in the first place. The moment the incident is over, the real job is to write the missing Dockerfile and a compose file so the next person can rebuild this thing from source in one command. If you are doing this for a PHP app, my walkthrough on a reproducible Docker setup for Laravel development covers the Dockerfile and compose layout I use in production, and if you are weighing how to run these images at scale, Kubernetes vs Docker Compose lays out when each one earns its keep. Back up the snapshot today, but spend tomorrow making sure you never need to commit a mystery container again.

