Back up Docker volumes for a 3D print library
Which volume holds the database, model files, and encryption key, why the built-in archive beats copying a live volume, and what a snapshot cannot restore.
Take the application’s own backup archive and mirror it off the host, then snapshot the named volumes with writes paused if you also want the machine’s exact state back. Copying a live volume directory while the stack is running is the one approach that looks like a backup and quietly is not, because a SQLite database copied mid-write can restore into a file that opens and is missing the last transactions.
For a Dockerized PrintStash there are five named volumes in the default stack, and only two of them matter for recovery. Knowing which two is most of the job.
What is in each volume
The default Compose file mounts these into the API container:
| Volume | Mounted at | Holds | Back it up |
|---|---|---|---|
printstash_db |
/data/db |
The SQLite database and the generated secrets key | Yes, this is the important one |
printstash_data |
/data/files |
Model, G-code, and document bytes PrintStash owns | Yes |
printstash_thumbs |
/data/thumbs |
Generated thumbnails | Optional, they regenerate |
printstash_staging |
/data/staging |
In-flight import scratch space | No |
printstash_backups |
/data/backups |
Archives the app writes | Copy them off, do not rely on them here |
Two more appear only with a profile enabled: printstash_postgres when you run the Postgres
profile instead of SQLite, and printstash_seaweedfs when you use the bundled S3-compatible
service. If you run either, that volume replaces or supplements the one above it, and the same rule
applies: back up whatever actually holds your bytes.
The detail worth pinning down is inside printstash_db. Alongside the database sits
.printstash-secrets-key, generated on first run when VAULT_SECRETS_KEY is not set. It is what
decrypts stored printer API keys, Bambu access codes, and the OIDC client secret. Restore a database
without it and the vault comes back with those fields unreadable.
Your .env file is not in any volume. It lives on the host next to the compose file, and it holds
VAULT_JWT_SECRET, storage credentials, and possibly VAULT_SECRETS_KEY itself. Back it up
separately, somewhere that is not a public repository.
The archive first, the snapshot second
PrintStash creates a full backup on demand through its API, and the archive holds the database, PrintStash-owned blobs, documents, thumbnails, and a manifest. Since v0.11.3 the SQLite side of that is a transactionally consistent snapshot including committed WAL data, with the blob set taken from that same snapshot.
That is the important difference from cp -r on a volume directory. The archive is coordinated with
the database; a filesystem copy of a running SQLite database is not, and the failure mode shows up
at restore time rather than at backup time.
So the routine that survives a bad day:
-
Pause the writers. Slicer post-processing hooks, scheduled imports, anything with an API key.
-
Trigger a full backup through the API. It needs a superuser; PrintStash does not run its own scheduler, so a cron job or a systemd timer is what makes it periodic.
-
Get the archive off the host. Mirroring to an S3-compatible bucket is built in, and automating backups to S3 covers the bucket settings and the scheduling call.
-
If you want the whole stack back rather than the library, snapshot
printstash_dbandprintstash_datatoo, with the API stopped:Terminal window docker compose stop apidocker run --rm -v printstash_db:/src:ro -v "$PWD":/out alpine \tar czf /out/printstash_db.tgz -C /src .docker run --rm -v printstash_data:/src:ro -v "$PWD":/out alpine \tar czf /out/printstash_data.tgz -C /src .docker compose start api -
Restore one into a scratch stack and open a model you recognize. Until that has happened you own an archive, not a recovery procedure.
What a volume snapshot will not give you back
Files you index in place are not in any of this. A NAS folder added as an external library is read
where it lives, PrintStash never owns those bytes, and removing a model or the volume never deletes
them. They are not in the archive and they are not in printstash_data either, so your NAS snapshot
policy has to cover them. That split is deliberate and it is the part people discover at the wrong
moment.
Restoring is a rollback, not a merge. The built-in restore validates and stages the database and blobs before applying anything, pauses concurrent writers while it works, and rolls storage changes back if it fails partway. What it does not do is reconcile a restored database against files added since the backup.
One change in v0.12.0 belongs in any conversation about storage layout: startup and hourly
maintenance no longer infer file ownership by deleting unindexed objects, and first-run local vault
paths must now be writable, dedicated, empty directories. If your
/data/files volume is a shared folder holding other things, move that content under External
Libraries instead.
Questions that come up
Can I just copy the folder under /var/lib/docker/volumes?
You can, and it is fine for the volumes holding plain files, but not for the database while the API is running. SQLite in WAL mode keeps recent transactions in a sidecar file, so a copy taken mid-write can produce a database that opens cleanly and is missing the most recent work, which is the worst kind of failure because nothing complains until you go looking for something. Stop the API container first, or use the built-in archive, which coordinates with the database instead of copying around it.
Do I have to stop the whole stack to take a snapshot?
Stopping the API container is enough, and it is the container that writes. The frontend holds no state. If you would rather not stop anything, take the built-in archive instead and accept that it covers PrintStash’s own data rather than the exact bytes on disk, which for most recoveries is what you actually wanted.
What happens if I restore the database without the secrets key?
The vault comes up, your models and history are intact, and every encrypted field fails to decrypt.
In practice that means stored printer API keys, Bambu LAN access codes, and the OIDC client secret
have to be entered again. Keep .printstash-secrets-key from inside printstash_db, or set
VAULT_SECRETS_KEY explicitly in .env and back that file up, which is the tidier arrangement
because it makes the key something you manage rather than something generated where you might not
look for it.
Are the archives in printstash_backups already safe?
No, they are on the same host and often the same disk as the data they protect. That covers a bad
upgrade or a mistaken deletion, which is the common case, and covers nothing about a failed disk, a
lost machine, or a docker compose down -v that takes the named volumes with it. Mirror them to a
bucket or another machine, and download one occasionally so a copy exists somewhere neither the app
nor its host can reach.
Does the backup include my NAS folder of STLs?
No, by design. A folder indexed as an external library stays yours: PrintStash reads it in place, writes new files into it without overwriting existing paths, and never deletes the source bytes. That means the archive covers the library’s metadata, revisions, outcomes, and history for those models, while the meshes themselves are covered by whatever backs up the NAS. Both halves need a plan, and only one of them is PrintStash’s job. Backups that actually restore covers the recovery model in full.
Sources
- PrintStash
docker-compose.ymlfor the named volumes and their mount points,backend/app/core/config.pyfor the backup directory and the default secrets key file path, andbackend/app/core/secrets.pyfor how stored secrets are encrypted, in the repository. Checked August 20, 2026. - The backup and restore guide for archive contents and restore behavior, and the v0.12.0 release notes for the startup maintenance and first-run vault path changes.