← Blog

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.

guidebackupsdockerself-hosted

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:

  1. Pause the writers. Slicer post-processing hooks, scheduled imports, anything with an API key.

  2. 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.

  3. 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.

  4. If you want the whole stack back rather than the library, snapshot printstash_db and printstash_data too, with the API stopped:

    Terminal window
    docker compose stop api
    docker 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
  5. 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.yml for the named volumes and their mount points, backend/app/core/config.py for the backup directory and the default secrets key file path, and backend/app/core/secrets.py for 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.